====== Module laby.py ======
===== Bien distinguer les différents éléments =====
Il nous faut distinguer deux choses :
* d'une part tout ce qui concerne les données relatives à un labyrinthe. Créer le labyrinthe d'après un fichier, déterminer si telle case contient un mur ou autre chose...
* d'autre part l'affichage à l'écran du labyrinthe. Celui-ci est une image fixe que l'on pourra gérer à l'aide d'un sprite pygame.
=== Pourquoi ne pas mélanger ? ===
Supposons que nous mettions la gestion des données relatives au labyrinthe et la gestion du sprite ensemble, dans un même objet.
* d'un côté, le mouvement du joueur est dépendant du labyrinthe : il ne peut pas traverser un mur.
* d'un autre côté, comme nous voulons qu'à l'affichage le joueur reste toujours au centre, c'est le labyrinthe qui bouge, alors le mouvement du labyrinthe est dépendant du joueur.
Nous avons alors une dépendance croisée qui va nous créer des ennuis.
De plus, le labyrinthe pourrait contenir des objets spéciaux, des bonus, qui pourraient nécessiter des sprites en plus. Le sprite du labyrinthe ne correspond qu'aux murs et aux objets fixes. Si on désire enrichir notre labyrinthe d'éléments nouveaux, on sera gênés.
Pour ces raisons, on préfère séparer : il y aura d'un côté un module ''laby.py'' chargé de la gestion des données relatives au labyrinthe, et un module ''background.py'' chargé de l'image de la partie fixe du labyrinthe.
===== Exemple de fichier labyrinthe =====
On aimerait pouvoir enregistrer un labyrinthe dans un fichier texte. Je propose la forme suivante :
***************************************************
* * * * * *
* * *** *** *** ******* * ******* * * *** * * *** *
* * * * * * * * * * * *
* * * *** *** * * *** * ***** * * **** **** *** * *
* * * * * D* * * * * * * *
* * *** * *********** *** ***** ********* *** * * *
* * * * * *
* * * * ********* ******* *** * * *************** *
* * * * * * *
* * * * *** *************** *** * * ******* * *****
* * * * * * * * *
* *** * * * * ******* * * *** ************* ***** *
* * * * * * * * * *
* ***** *** ******* * * ******* * * ********* *** *
* * * * * *
***** *** * ********* ******* *** *****************
* * * * * * * * *
* * *** * * * * ***** * *** * ********* * * ***** *
* * * * * * * * * * *
* * * ******* *** * ***** * *** *** ***** ***** * *
* * * * * * * * * *
* ******* * *** *************** * *** * ********* *
* * * * *
***************************************************
On pourra placer les différents niveaux dans un dossier ''levels''. Celui-ci serait par exemple ''level1.txt''.
Dans le labyrinthe, les ''*'' représentent les murs et le ''D'' est le point de départ du joueur.
===== Implementation =====
# laby.py
from abc import abstractmethod
class Laby:
FOLDER = 'levels/'
EXT = '.txt'
WALL = '*'
START = 'D'
EMPTY = ' '
@abstractmethod
def load(level_name:str):
"""
level_name: nom du fichier niveau. Par exemple 'level1'
renvoie le Laby correspondant
"""
filename = Laby.FOLDER + level_name + Laby.EXT
with open(filename, 'r', encoding='utf8') as file:
lines = file.read().split('\n')
# suppression de dernières lignes vides :
while lines != [] and lines[-1] == '':
lines.pop()
return Laby(lines)
def get_start(self):
"""
renvoie les coordonnées du point de départ
"""
for i_lig, line in enumerate(self.grid):
if self.START in line:
i_col = line.index(self.START)
return i_lig, i_col
return -1, -1
def __init__(self, grid):
"""
grid: tableau de chaines représentant la grille de jeu
"""
self.h = len(grid)
assert self.h != 0
# égalisation de la taille des lignes
# en complétant avec des vides
m = max([len(line) for line in grid])
assert m!= 0
self.w = m
for i, line in enumerate(grid):
n = len(line)
if n < m:
grid[i] = line + Laby.EMPTY*(m - n)
self.grid = grid
self.start = self.get_start()
assert self.start != (-1, -1)
def get_cell(self, line:int, col:int) -> str:
"""
line, col: position sur la grille
renvoie le contenu dans la cellule correspondante
voir plus bas pour la justification des modulos
"""
return self.grid[line%self.h][col%self.w]
def is_wall(self, line:int, col:int) -> bool:
"""
renvoie True si la cellule contient un mur
"""
return self.get_cell(line, col) == self.WALL
def get_walls_coords(self):
"""
renvoie la liste des (i_lig, i_col) des murs
"""
liste = []
for i_lig, line in enumerate(self.grid):
for i_col, car in enumerate(line):
if car == self.WALL:
liste.append((i_lig, i_col))
return liste
=== Fonction get_cell ===
Le monde de Pac-Man est « torique ». Cela veut dire que le bord haut touche le bord bas et le bord gauche touche le bord droit.
Si par exemple la grille contient 40 lignes, indicées de 0 à 39, si je suis sur la dernière ligne (39 donc) et que je descend d'une case, je dois apparaître en haut, à ligne 0. Cela veut dire que l'indice 40 est en quelque sort équivalent à l'indice 0. Et si je descend encore de 1, je serait à la ligne 41 ou la ligne 1. etc. On reconnaît le fonctionnement du modulo.
Ce qui vaut pour les lignes est également valable pour les colonnes.
=== Fonction get_walls_coords ===
Quand on créera l'image pour le labyrinthe, on voudra créer un petit carré pour chaque cellule murée. On facilite le travail avec cette fonction qui renvoie la liste des indices de toutes les cellules avec mur.