class CanvasEchiquier(Canvas): """Classe héritant d'un Canvas, et affichant un échiquier qui se redimensionne automatique lorsque la fenêtre est étirée. """ def __init__(self, parent, n_pixels_par_case): # Nombre de lignes et de colonnes. self.n_lignes = 8 self.n_colonnes = 8 # Noms des lignes et des colonnes. self.chiffres_rangees = ['1', '2', '3', '4', '5', '6', '7', '8'] self.lettres_colonnes = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] # Nombre de pixels par case, variable. self.n_pixels_par_case = n_pixels_par_case # Appel du constructeur de la classe de base (Canvas). # La largeur et la hauteur sont déterminées en fonction du nombre de cases. super().__init__(parent, width=self.n_lignes * n_pixels_par_case, height=self.n_colonnes * self.n_pixels_par_case) # On fait en sorte que le redimensionnement du canvas redimensionne son contenu. Cet événement étant également # généré lors de la création de la fenêtre, nous n'avons pas à dessiner les cases et les pièces dans le # constructeur. self.echiquier = Echiquier() self.echiquier.initialiser_echiquier_depart() self.bind('<Configure>', self.redimensionner) #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% def dessiner_cases(self): """Méthode qui dessine les cases de l'échiquier. """ for i in range(self.n_lignes): for j in range(self.n_colonnes): debut_ligne = i * self.n_pixels_par_case fin_ligne = debut_ligne + self.n_pixels_par_case debut_colonne = j * self.n_pixels_par_case fin_colonne = debut_colonne + self.n_pixels_par_case # On détermine la couleur. if (i + j) % 2 == 0: couleur = 'white' else: couleur = 'gray' # On dessine le rectangle. On utilise l'attribut "tags" pour être en mesure de récupérer les éléments # par la suite. self.create_rectangle(debut_colonne, debut_ligne, fin_colonne, fin_ligne, fill=couleur, tags='case') #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% def dessiner_pieces(self): # Caractères unicode représentant les pièces. Vous avez besoin de la police d'écriture DejaVu. self.caracteres_pieces = { Pion('blanc'): '\u2659', Pion('noir'): '\u265f', Tour('blanc'): '\u2656', Tour('noir'): '\u265c', Cavalier('blanc'): '\u2658', Cavalier('noir'): '\u265e', Fou('blanc'): '\u2657', Fou('noir'): '\u265d', Roi('blanc'): '\u2654', Roi('noir'): '\u265a', Dame('blanc'): '\u2655', Dame('noir'): '\u265b' } # Pour tout paire position, pièce: for position in self.echiquier.dictionnaire_pieces: # On dessine la pièce dans le canvas, au centre de la case. On utilise l'attribut "tags" pour être en # mesure de récupérer les éléments dans le canvas. coordonnee_y = ( self.n_lignes - self.chiffres_rangees.index(position[1]) - 1) * self.n_pixels_par_case + self.n_pixels_par_case // 2 coordonnee_x = self.lettres_colonnes.index(position[ 0]) * self.n_pixels_par_case + self.n_pixels_par_case // 2 piece = self.echiquier.dictionnaire_pieces[position] uni = "" for e in list(self.caracteres_pieces.keys()): if type(e) == type(piece) and e.couleur == piece.couleur: uni = self.caracteres_pieces[e] break self.create_text(coordonnee_x, coordonnee_y, text=uni, font=('Deja Vu', self.n_pixels_par_case // 2), tags='piece') #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% def redimensionner(self, event): # Nous recevons dans le "event" la nouvelle dimension dans les attributs width et height. On veut un damier # carré, alors on ne conserve que la plus petite de ces deux valeurs. nouvelle_taille = min(event.width, event.height) # Calcul de la nouvelle dimension des cases. self.n_pixels_par_case = nouvelle_taille // self.n_lignes # On supprime les anciennes cases et on ajoute les nouvelles. self.delete('case') self.dessiner_cases() # On supprime les anciennes pièces et on ajoute les nouvelles. self.delete('piece') self.dessiner_pieces()
class CanvasEchiquier(Canvas): """Classe héritant d'un Canvas, et affichant un échiquier qui se redimensionne automatique lorsque la fenêtre est étirée. """ def __init__(self, parent, n_pixels_par_case, store_filename='annulerdeplacement.txt'): # Nombre de lignes et de colonnes. self.n_lignes = 8 self.n_colonnes = 8 # Noms des lignes et des colonnes. self.chiffres_rangees = ['1', '2', '3', '4', '5', '6', '7', '8'] self.lettres_colonnes = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] # Nombre de pixels par case, variable. self.n_pixels_par_case = n_pixels_par_case # Appel du constructeur de la classe de base (Canvas). # La largeur et la hauteur sont déterminés en fonction du nombre de cases. super().__init__(parent, width=self.n_lignes * n_pixels_par_case, height=self.n_colonnes * self.n_pixels_par_case) # On fait en sorte que le redimensionnement du canvas redimensionne son contenu. Cet événement étant également # généré lors de la création de la fenêtre, nous n'avons pas à dessiner les cases et les pièces dans le # constructeur. self.bind('<Configure>', self.redimensionner) # Importation de Échequier et partie et Fenetre self.echequier = Echiquier() self.partie = Partie() self.fenetre = Fenetre # Dictionnaire contenant les pièces. Vous devinerez, si vous réutilisez cette classe dans votre TP4, # qu'il fandra adapter ce code pour plutôt utiliser la classe Echiquier. self.pieces = self.echequier.dictionnaire_pieces # Les variables nécessaires pour la fonction déplacement self.selectionner = 0 self.case_source = "" self.case_cible = "" # Déterminer le gagnant self.gagnant_noir = False self.gagnant_blanc = False # Enregistrement des coups self.texte = "" # Fichier text annuler déplacement self.store_filename = os.path.join(BASE_DIR, store_filename) self.t = 0 def dessiner_cases(self): """Méthode qui dessine les cases de l'échiquier. """ for i in range(self.n_lignes): for j in range(self.n_colonnes): debut_ligne = i * self.n_pixels_par_case fin_ligne = debut_ligne + self.n_pixels_par_case debut_colonne = j * self.n_pixels_par_case fin_colonne = debut_colonne + self.n_pixels_par_case position = self.obtenir_position_a_partir_de_coordonnes(i, j) position_selectione = self.case_source # Les couleurs if position == position_selectione: couleur = "yellow" elif (i + j) % 2 == 0: couleur = 'white' else: couleur = 'gray' # On dessine le rectangle. On utilise l'attribut "tags" pour être en mesure de récupérer les éléments # par la suite. self.create_rectangle(debut_colonne, debut_ligne, fin_colonne, fin_ligne, fill=couleur, tags='case') def dessiner_pieces(self): # Caractères unicode représentant les pièces. Vous avez besoin de la police d'écriture DejaVu. caracteres_pieces = { 'PB': '\u2659', 'PN': '\u265f', 'TB': '\u2656', 'TN': '\u265c', 'CB': '\u2658', 'CN': '\u265e', 'FB': '\u2657', 'FN': '\u265d', 'RB': '\u2654', 'RN': '\u265a', 'DB': '\u2655', 'DN': '\u265b' } # Pour tout paire position, pièce: for position, piece in self.echequier.dictionnaire_pieces.items(): # On dessine la pièce dans le canvas, au centre de la case. On utilise l'attribut "tags" pour être en # mesure de récupérer les éléments dans le canvas. coordonnee_y = ( self.n_lignes - self.chiffres_rangees.index(position[1]) - 1) * self.n_pixels_par_case + self.n_pixels_par_case // 2 coordonnee_x = self.lettres_colonnes.index(position[ 0]) * self.n_pixels_par_case + self.n_pixels_par_case // 2 self.create_text(coordonnee_x, coordonnee_y, text=[piece], font=('Deja Vu', self.n_pixels_par_case // 2), tags='piece') def redimensionner(self, event): # Nous recevons dans le "event" la nouvelle dimension dans les attributs width et height. On veut un damier # carré, alors on ne conserve que la plus petite de ces deux valeurs. nouvelle_taille = min(event.width, event.height) # Calcul de la nouvelle dimension des cases. self.n_pixels_par_case = nouvelle_taille // self.n_lignes # On supprime les anciennes cases et on ajoute les nouvelles. self.delete('case') self.dessiner_cases() # On supprime les anciennes pièces et on ajoute les nouvelles. self.delete('piece') self.dessiner_pieces() def obtenir_position_a_partir_de_coordonnes(self, i, j): position = "{}{}".format( self.lettres_colonnes[j], int(self.chiffres_rangees[self.n_lignes - i - 1])) return position def deplacer(self, source, cible): self.echequier.deplacer(source, cible) self.rafraichir() self.echequier.dictionnaire_pieces.update() def rafraichir(self): # On supprimer les anciennes cases et on ajouter les nouvelles. self.delete("case") self.dessiner_cases() # On supprimer les anciennes pièces et on ajoute les nouvelles. self.delete("piece") self.dessiner_pieces() def jouer(self, event): x, y = event.x, event.y ligne = y // self.n_pixels_par_case colonne = x // self.n_pixels_par_case position = "{}{}".format( self.lettres_colonnes[colonne], int(self.chiffres_rangees[self.n_lignes - ligne - 1])) if self.selectionner == 1: self.case_cible = position try: self.deplacer(self.case_source, self.case_cible) if self.echequier.roi_de_couleur_est_dans_echiquier( "blanc") is False: messagebox.showinfo("Félicitation!", "Le joueur noir a gagné la partie!") self.unbind("<Button-1>"), self.jouer elif self.echequier.roi_de_couleur_est_dans_echiquier( "noir") is False: messagebox.showinfo("Félicitation!", "Le joueur blanc a gagné la partie!") self.unbind("<Button-1>"), self.jouer elif self.echequier.recuperer_piece_a_position( self.case_source) is None: self.partie.joueur_suivant() self.unbind("<Button-1>"), self.jouer elif self.echequier.deplacement_est_valide(self.case_source, self.case_cible) is False and \ self.echequier.recuperer_piece_a_position(self.case_cible) is None: raise DeplacementImpossible elif self.echequier.deplacement_est_valide(self.case_source, self.case_cible) is False and \ self.echequier.recuperer_piece_a_position(self.case_cible) is not None: if self.echequier.couleur_piece_a_position( self.case_cible ) == self.echequier.couleur_piece_a_position( self.case_source): pass else: raise PriseImpossible self.texte += "{} {} à {} \n".format( self.echequier.recuperer_piece_a_position(self.case_cible), self.case_source, self.case_cible) except DeplacementImpossible: messagebox.showinfo("Erreur", "Déplacement impossible") except PriseImpossible: messagebox.showinfo("Erreur", "Prise impossible") self.case_source = "" self.case_cible = "" self.selectionner = 0 self.rafraichir() elif self.selectionner == 0: try: if self.echequier.couleur_piece_a_position( position) == self.partie.joueur_actif: self.case_source = position self.rafraichir() self.selectionner += 1 elif self.echequier.couleur_piece_a_position(position) != self.partie.joueur_actif and \ self.echequier.recuperer_piece_a_position(position) is not None: raise SelectionMauvaisePiece except SelectionMauvaisePiece: messagebox.showinfo( "Erreur", "Selection impossible: c'est au tour du joueur {}".format( self.partie.joueur_actif)) else: self.selectionner = 0 self.case_source = "" self.case_cible = "" def sauvegarder(self, filename): with open(filename, 'wb') as f: pickle.dump(self.echequier.dictionnaire_pieces, f) def charger(self, filename): with open(filename, 'rb') as f: data = pickle.load(f) f.close() self.echequier.dictionnaire_pieces = data self.rafraichir() def nouvelle_partie(self): self.echequier.initialiser_echiquier_depart() if self.partie.joueur_actif == "noir": self.partie.joueur_actif = "blanc" self.texte = "" self.rafraichir() def sauvegarder_mouvement(self, filename): with open(filename, 'wb') as f: pickle.dump(self.texte, f) def charger_mouvement(self, filename): with open(filename, 'rb') as f: data = pickle.load(f) f.close() self.texte = data self.rafraichir() def annuler_mouvement(self): derniere_ligne = self.texte[-9:] case_cible_avec = derniere_ligne[-4:] case_cible = case_cible_avec[0:2] case_source = derniere_ligne[0:3] self.echequier.deplacement_absolue(case_cible, case_source) self.rafraichir() self.texte = self.texte[0:-11] def revoir_une_partie(self, filename): self.nouvelle_partie() with open(filename, 'rb') as f: data = pickle.load(f) f.close() self.texte = data partie_en_text = self.texte Nombre_de_ligne = len(partie_en_text) // 11 while Nombre_de_ligne > 0: ligne = partie_en_text[2:11] case_source = ligne[0:2] case_cible = ligne[-4:] self.echequier.deplacement_absolue(case_source, case_cible) Nombre_de_ligne -= 1 partie_en_text = partie_en_text[11:] self.rafraichir() self.update() self.after(1500) pass
class Partie: """ La classe Partie contient les informations sur une partie d'échecs, c'est à dire un échiquier, puis un joueur actif (blanc ou noir). Des méthodes sont disponibles pour faire avancer la partie et interagir avec l'utilisateur. Attributes: joueur_actif (str): La couleur du joueur actif, 'blanc' ou 'noir'. echiquier (Echiquier): L'échiquier sur lequel se déroule la partie. """ def __init__(self): # Le joueur débutant une partie d'échecs est le joueur blanc. self.joueur_actif = 'blanc' # Création d'une instance de la classe Echiquier, qui sera manipulée dans les méthodes de la classe. self.echiquier = Echiquier() #intialisation de deux listes qui permettront d'afficher les derniers déplacements self.listeDeplacements = [] self.dernierDeplacement = [] self.hist = [] self.nom_fichier_sauvegarde = 'sauvegarde' def determiner_gagnant(self): """Détermine la couleur du joueur gagnant, s'il y en a un. Pour déterminer si un joueur est le gagnant, le roi de la couleur adverse doit être absente de l'échiquier. Returns: str: 'blanc' si le joueur blanc a gagné, 'noir' si c'est plutôt le joueur noir, et 'aucun' si aucun joueur n'a encore gagné. """ if not self.echiquier.roi_de_couleur_est_dans_echiquier('noir'): return 'blanc' elif not self.echiquier.roi_de_couleur_est_dans_echiquier('blanc'): return 'noir' return 'aucun' def partie_terminee(self): """Vérifie si la partie est terminée. Une partie est terminée si un gagnant peut être déclaré. Returns: bool: True si la partie est terminée, et False autrement. """ return self.determiner_gagnant() != 'aucun' def annulerDernierMouvement(self): """ Permet d'annuler le dernier mouvement et de rafraichir les pieces blanches et noires qui restent. Un premier scénario ou la liste a 2 ou moins historique d'échiquier. Un deuxième scénario dans le cas contraire. """ if len(self.echiquier.listeDesEchiquiers) <= 2: self.echiquier.initialiser_echiquier_depart() self.joueur_actif = 'blanc' else: del self.echiquier.listeDesEchiquiers[-1] self.echiquier.dictionnaire_pieces = self.echiquier.listeDesEchiquiers[ -1] self.joueur_suivant() self.resteBlanc = set() self.resteNoir = set() for i in self.echiquier.dictionnaire_pieces.values(): if i.est_blanc(): self.resteBlanc.add(i) else: self.resteNoir.add(i) self.gapBlanc = list(self.echiquier.setBlanc - self.resteBlanc) self.gapNoir = list(self.echiquier.setNoir - self.resteNoir) def roque_est_valide(self, position_source, position_cible): """ Identifie si le Roi peut effectuer un Roque. Pour pouvoir effectuer un Roque, il faut que: 1. La pièece à la position_cible soit une Tour 2. La pièce à la position_source soit un Roi 3. Ni le Roi ni la Tour n'aient effectué de mouvement depuis le début de la partie. 4. La voie doit être libre (aucune pièce) entre le Roi et la Tour. 5. Aucune case entre le Roi et la Tour ne soit menacée par l'adversaire. 6. Le roi ne soit pas en échec. 7. La tour ne soit pas menacée. Args: position_source (str): Position du Roi position_cible (str): Position de la Tour Returns: bool: True si le Roi peut Roquer, et False autrement. """ couleur_adversaire = 'blanc' rangee_origine = '8' piece = self.echiquier.recuperer_piece_a_position(position_source) piece_cible = self.echiquier.recuperer_piece_a_position(position_cible) if piece.couleur == 'blanc': couleur_adversaire = 'noir' rangee_origine = '1' #critères de 1 à 3 if isinstance(piece, Roi) and isinstance(piece_cible, Tour) and\ piece.couleur == piece_cible.couleur and\ self.echiquier.recuperer_piece_a_position(position_source) not in self.hist and \ self.echiquier.recuperer_piece_a_position(position_cible) not in self.hist: if position_cible[0] == 'a': #Grand Roque for colonne in self.echiquier.lettres_colonnes[0:5]: #critères 5 à 7 if self.echiquier.case_est_menacee_par( colonne + rangee_origine, couleur_adversaire): return False return True else: #Petit Roque for colonne in self.echiquier.lettres_colonnes[4:]: # critères 5 à 7 if self.echiquier.case_est_menacee_par( colonne + rangee_origine, couleur_adversaire): return False return True def roquer(self, position_source, position_cible): """ Effectue le mouvement de Roque si celui-ce est valide dans l'échiquier. Args: position_source (str): position du Roi dans l'échiquier position_cible (str): position de la Tour dans l'échiquier. """ self.joueur_suivant() if ord(position_source[0]) > ord(position_cible[0]): position_roi = 'c' + position_source[1] self.echiquier.dictionnaire_pieces[position_roi] = \ self.echiquier.recuperer_piece_a_position(position_source) del self.echiquier.dictionnaire_pieces[position_source] position_tour = 'd' + position_cible[1] self.echiquier.dictionnaire_pieces[position_tour] = \ self.echiquier.recuperer_piece_a_position(position_cible) del self.echiquier.dictionnaire_pieces[position_cible] else: position_roi = 'g' + position_source[1] self.echiquier.dictionnaire_pieces[position_roi] = \ self.echiquier.recuperer_piece_a_position(position_source) del self.echiquier.dictionnaire_pieces[position_source] position_tour = 'f' + position_cible[1] self.echiquier.dictionnaire_pieces[position_tour] = \ self.echiquier.recuperer_piece_a_position(position_cible) del self.echiquier.dictionnaire_pieces[position_cible] #Trucs a Thierry piece = self.echiquier.recuperer_piece_a_position(position_source) self.dernierDeplacement = [ "(" + self.joueur_actif + ")" + position_source + "->" + position_cible ] self.listeDeplacements.append(self.dernierDeplacement) echiquierCopy = dict(self.echiquier.dictionnaire_pieces) self.echiquier.listeDesEchiquiers.append(echiquierCopy) self.resteBlanc = set() self.resteNoir = set() for i in self.echiquier.dictionnaire_pieces.values(): if (i.est_blanc()): self.resteBlanc.add(i) else: self.resteNoir.add(i) self.gapBlanc = list(self.echiquier.setBlanc - self.resteBlanc) self.gapNoir = list(self.echiquier.setNoir - self.resteNoir) def deplacer(self, position_source, position_cible): """ Permet de d'appeler le mouvement à la méthode déplacer du module Échiquier. Permet aussi de recalculer les pièces mangées qui sert dans le visuel du jeux. En même temps, sert à écrire ce qui sera communiquer au joueur sur la liste des déplacements dans le Listbox du jeux. Args: position_source (str): Position de source de la pièce position_cible (str): Position cible de la pièce """ piece = self.echiquier.recuperer_piece_a_position(position_source) #Pour le roque if self.echiquier.deplacement_est_valide(position_source, position_cible): self.hist.append(piece) self.echiquier.deplacer(position_source, position_cible) self.joueur_suivant() self.dernierDeplacement = [ "(" + piece.couleur + ")" + position_source + "->" + position_cible ] self.listeDeplacements.append(self.dernierDeplacement) echiquierCopy = dict(self.echiquier.dictionnaire_pieces) self.echiquier.listeDesEchiquiers.append(echiquierCopy) self.dictionnaire_pieces_initial = { 'a1': Tour('blanc'), 'b1': Cavalier('blanc'), 'c1': Fou('blanc'), 'd1': Dame('blanc'), 'e1': Roi('blanc'), 'f1': Fou('blanc'), 'g1': Cavalier('blanc'), 'h1': Tour('blanc'), 'a2': Pion('blanc'), 'b2': Pion('blanc'), 'c2': Pion('blanc'), 'd2': Pion('blanc'), 'e2': Pion('blanc'), 'f2': Pion('blanc'), 'g2': Pion('blanc'), 'h2': Pion('blanc'), 'a7': Pion('noir'), 'b7': Pion('noir'), 'c7': Pion('noir'), 'd7': Pion('noir'), 'e7': Pion('noir'), 'f7': Pion('noir'), 'g7': Pion('noir'), 'h7': Pion('noir'), 'a8': Tour('noir'), 'b8': Cavalier('noir'), 'c8': Fou('noir'), 'd8': Dame('noir'), 'e8': Roi('noir'), 'f8': Fou('noir'), 'g8': Cavalier('noir'), 'h8': Tour('noir'), } self.setBlanc = set() self.setNoir = set() for i in self.dictionnaire_pieces_initial.values(): if i.est_blanc(): self.setBlanc.add(i) else: self.setNoir.add(i) self.resteBlanc = set() self.resteNoir = set() for i in self.echiquier.dictionnaire_pieces.values(): if i.est_blanc(): self.resteBlanc.add(i) else: self.resteNoir.add(i) self.gapBlanc = list(self.echiquier.setBlanc - self.resteBlanc) self.gapNoir = list(self.echiquier.setNoir - self.resteNoir) def joueur_suivant(self): """Change le joueur actif: passe de blanc à noir, ou de noir à blanc, selon la couleur du joueur actif. """ if self.joueur_actif == 'blanc': self.joueur_actif = 'noir' else: self.joueur_actif = 'blanc' def position_mon_roi(self, couleur_joueur_actif): """ Identifie ou se trouve le roi de la couleur entrée en argument dans l'échiquier. Args: couleur_joueur_actif (str): couleur du joueur dont on souhaite connaître la position du Roi. Returns: str: Retourne la position du roi dans l'échiquier. """ for position in self.echiquier.dictionnaire_pieces.keys(): if isinstance(self.echiquier.dictionnaire_pieces[position], Roi) \ and self.echiquier.dictionnaire_pieces[position].couleur == couleur_joueur_actif: return position def mon_roi_en_echec(self): """ Identifie si le Roi du joueur actif est en échec. Par en échec on entend que sa case est menacée par le joueur adverse. Si le joueur actif ne réagit pas à la menace, il perdra la partie au prochain tour. Returns: bool: True si le Roi du joueur actif est en échec, false autrement. """ position_roi = self.position_mon_roi(self.joueur_actif) if self.joueur_actif == 'blanc': autre_couleur = 'noir' else: autre_couleur = 'blanc' if self.partie_terminee(): return False return self.echiquier.case_est_menacee_par(position_roi, autre_couleur) def sauvegarder_partie(self): """ Permet de sauver la partie. """ with open(self.nom_fichier_sauvegarde, "wb") as f: pickle.dump(self.echiquier.dictionnaire_pieces, f) def charger_partie(self): """ Permet de charger une partie sauvegarder avec la méthode sauvergarder_partie """ with open(self.nom_fichier_sauvegarde, "rb") as f: self.echiquier.dictionnaire_pieces = pickle.load(f)