from tkinter import * from logique import LogiqueEchecs class PlateauTk: """ Plateau de jeu de damier en canvas Tk """ RATIO_PCE_CASE = .95 # Place que prend la pièce sur la case TEMPS_ANIM = 200 # Temps en ms d'une animation INTER_ANIM = 10 # Temps en ms entre deux actualisations d'animation COTE_TOUT_DEFAUT = 500 # Coté par défaut du plateau def __init__(self, fen, can, statut, logique): """ Constructeur de PlateauTk """ self.can = can # Canvas self.fen = fen # Fenêtre Tk (nécessaire pour lancer un timeout pour l'animation) self.statut = statut # Fonction qui envoie un message à l'utilisateur self.grilleDamier = [] # Tableau contenant les références aux cases du damier self.grillePieces = [] # Tableau contenant les références aux pièces self.photos = [] # Liste contenant les différentes photos utilisées (afin qu'elles ne soient pas recyclées) self.imagesOriginales = {} # Dictionnaire associant chaque pièce à son image originale self.imagesRedim = {} # Dictionnaire associant chaque pièce à son image redimensionnée self.animations = [] # Liste contenant les animations en cours self.dEtape = True # Étape du déplacement (True : prendre la pièce, False : poser la pièce) self.dx1 = -1 # Coordonnées X de la pièce sélectionnée self.dy1 = -1 # Coordonnées Y de la pièce sélectionnée self.dx2 = -1 # Coordonnées X de la destination self.dy2 = -1 # Coordonnées Y de la destination self.mvtsPossibles = [] # Liste des mouvements possibles pour la pièce sélectionnée self.coteCase = 0 # Coté d'une case self.logique = logique # Logique du jeu self.importerImages() self.redimCan(self.COTE_TOUT_DEFAUT) self.can.bind('', self.clic) self.statutPrendre() def redimCan(self, cote): """ Redimensionne le plateau de jeu """ self.can.delete(ALL) self.coteCase = cote // self.logique.CASES_COTE cote = self.logique.CASES_COTE * self.coteCase self.can.config(width=cote, height=cote) for i in self.imagesRedim: del i self.redimImages() self.cDamier() self.cGrille() self.remplirGrille() def nomPiece(self, piece): """ Renvoi le nom de fichier de la pièce donnée """ tPiece = self.logique.tPiece(piece) if tPiece == self.logique.PCE_PION: return 'pion' elif tPiece == self.logique.PCE_TOUR: return 'tour' elif tPiece == self.logique.PCE_CAVALIER: return 'cavalier' elif tPiece == self.logique.PCE_FOU: return 'fou' elif tPiece == self.logique.PCE_DAME: return 'dame' elif tPiece == self.logique.PCE_ROI: return 'roi' else: return False def importerImage(self, piece, couleur): # TODO piece stocke déjà la couleur """ Importe l'image de la pièce donnée """ nom = 'sprites/'+self.nomPiece(piece)+couleur+'.gif' self.imagesOriginales.update({piece:PhotoImage(file=nom)}) def importerImages(self): """ Importe les images des pièces du jeu """ for piece in self.logique.BLANCS: self.importerImage(piece, 'B') for piece in self.logique.NOIRS: self.importerImage(piece, 'N') def redimImage(self, piece, sample): """ Redimensionne l'image de la pièce donnée """ self.imagesRedim.update({piece:self.imagesOriginales[piece].subsample(sample)}) def redimImages(self): """ Redimensionne l'image des pièces du jeu """ sample = int(504 // (self.coteCase * self.RATIO_PCE_CASE)) # TODO S'en sort pour être toujours plus grand for piece in self.logique.BLANCS: self.redimImage(piece, sample) for piece in self.logique.NOIRS: self.redimImage(piece, sample) # Dessin @staticmethod def caseCouleur(blanc, contexte=0): """ Retourne la couleur hexadécimale de la case de couleur et de contexte donnée """ if contexte == 1: # Sélectionné return '#a0cefe' if blanc else '#478bd1' elif contexte == 2: # Possible / Victoire return '#bafea0' if blanc else '#6ed147' elif contexte == 3: # Impossible / Défaite return '#fea0ab' if blanc else '#d14758' else: # Normal return '#ffce9e' if blanc else '#d18b47' def cCase(self, x, y): """ Crée la case aux coordonnées données """ couleur = self.caseCouleur(self.logique.eCaseBlanche(x, y), 0) return self.can.create_rectangle(x * self.coteCase, y * self.coteCase, (x + 1) * self.coteCase, (y + 1) * self.coteCase) def coulCase(self, x, y, contexte=0): """ Colorie la case aux coordonnées données selon le contexte donné """ couleur = self.caseCouleur(self.logique.eCaseBlanche(x, y), contexte) self.can.itemconfig(self.grilleDamier[x][y], fill=couleur, outline=couleur) def coulDamier(self, contexte=0): """ Colorie toutes les cases selon le contexte donné """ for x in range(0, self.logique.CASES_COTE): for y in range(0, self.logique.CASES_COTE): self.coulCase(x, y, contexte) def cDamier(self): """ Génère le damier """ self.grilleDamier = [] for x in range(0, self.logique.CASES_COTE): # TODO Peut être amélioré colonne = [] for y in range(0, self.logique.CASES_COTE): colonne.append(self.cCase(x, y)) self.grilleDamier.append(colonne) self.coulDamier() def cPiece(self, x, y, piece): """ Crée la pièce aux coordonnées données """ if self.logique.ePiece(piece): self.grillePieces[x][y] = self.can.create_image((x + .5) * self.coteCase, (y + .5) * self.coteCase, image=self.imagesRedim[piece]) else: self.grillePieces[x][y] = False def cGrille(self): """ Crée le tableau contenant les images de pièces """ self.grillePieces = [] for x in range(0, self.logique.CASES_COTE): # Crée self.grillePieces colonne = [] for y in range(0, self.logique.CASES_COTE): colonne.append(False) self.grillePieces.append(colonne) def remplirGrille(self): """ Remplis la grille des images de pièce selon la grille des pièces logiques. """ for x in range(0, self.logique.CASES_COTE): # Remplis self.grillePieces for y in range(0, self.logique.CASES_COTE): self.cPiece(x, y, self.logique.grille[x][y]) # Interaction def nomJoueur(self, joueur, pluriel=True): """ Retourne le nom du joueur donné """ if joueur == self.logique.BLANC: nom = 'blanc' elif joueur == self.logique.NOIR: nom = 'noir' else: nom = 'inconnu' if pluriel: nom += 's' return nom def statutPrendre(self): """ Change le statut indiquant au joueur en cours de jouer. """ self.statut('Aux ' + self.nomJoueur(self.logique.joueur, pluriel=True) + ' de sélectionner une pièce.') @staticmethod def animationDCoords(i): """ Retourne les coordonnées auxquelles doit se placer une pièce en cours d'animation """ # TODO Faire autre chose qu'une animation linéaire constante x = i['x1'] + (i['x2']-i['x1']) * (i['avancement']/i['total']) y = i['y1'] + (i['y2']-i['y1']) * (i['avancement']/i['total']) return [x, y] def animation(self): """ Effectue les changements graphique nécessaire à la création d'une image d'animation. """ animationsNv = [] for i in self.animations: if i['avancement'] < i['total']: if i['type'] == 'd': coords = self.animationDCoords(i) self.can.coords(i['piece'], coords[0], coords[1]) # elif i['type'] == 'f': # TODO Opacité de i['piece'] # elif i['type'] == 'c': # TODO Opacité de case i['avancement'] += self.INTER_ANIM animationsNv.append(i) else: if i['type'] == 'd': self.can.coords(i['piece'], i['x2'], i['y2']) elif i['type'] == 'f': self.can.delete(i['piece']) elif i['type'] == 'c': self.coulCase(i['x'], i['y'], 0) self.animations = animationsNv if len(animationsNv): # Si il y aura encore des animations à faire self.fen.after(self.INTER_ANIM, self.animation) # On prévoit une image (boucle) def animer(self, animation): """ Ajoute une animation à la liste des animations en cours, et lance la fonction animation() si la boucle n'est déjà pas lancée. """ etaitVide = len(self.animations) < 1 self.animations.append(animation) if etaitVide: self.animation() def animerD(self, x1, y1, x2, y2, piece): """ Ajoute une animation pour un déplacment de piece """ if len(self.animations): for i in self.animations: if i['type'] == 'd' and i['piece'] == piece: # Si une animation pour cette pièce existe déjà, on la reprend et on la modifie coords = self.animationDCoords(i) i['x1'] = coords[0] # TODO Simplifier avec update() i['y1'] = coords[1] i['x2'] = x2 i['y2'] = y2 # i['total'] = i['total'] - i['avancement'] i['total'] = self.TEMPS_ANIM i['avancement'] = 0 return animation = { 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2, 'piece': piece, 'type': 'd', 'total': self.TEMPS_ANIM, 'avancement': 0 } self.can.tag_raise(piece) # Mise au premier plan self.animer(animation) def animerF(self, piece): """ Ajoute une animation pour la suppression d'une pièce """ animation = { 'piece': piece, 'type': 'f', 'total': self.TEMPS_ANIM, 'avancement': 0 } self.animer(animation) def animerC(self, x ,y): """ Ajoute une animation pour l'effacement de la surbrillance d'une case """ animation = { 'type': 'c', 'x': x, 'y': y, 'total': self.TEMPS_ANIM, 'avancement': 0 } self.animer(animation) def victoire(self): """ Indique à l'utilisateur la victoire et décore le plateau pour l'occasion """ self.statut('Victoire des ' + self.nomJoueur(self.logique.victorieux) + ' !') self.coulDamier() for x in range(0, self.logique.CASES_COTE): for y in range(0, self.logique.CASES_COTE): piece = self.logique.grille[x][y] if self.logique.ePiece(piece): if self.logique.ePieceNoir(piece) ^ self.logique.victorieux: self.coulCase(x, y, 2) else: self.coulCase(x, y, 3) def dPiece(self, x1, y1, x2, y2): """ Déplace la pièce aux coordonnées 1 aux coordonnées 2. (Vérifie la possibilité du mouvement et crée les animations nécessaires) """ test = self.logique.dPiece(x1, y1, x2, y2) if test['valide'] == True: # Si déplacement possible for s in test['supprimer']: self.animerF(self.grillePieces[s[0]][s[1]]) for d in test['deplacer']: self.grillePieces[d[2]][d[3]], self.grillePieces[d[0]][d[1]] = \ self.grillePieces[d[0]][d[1]], False self.animerD((d[0] + .5) * self.coteCase, (d[1] + .5) * self.coteCase, \ (d[2] + .5) * self.coteCase, (d[3] + .5) * self.coteCase, \ self.grillePieces[d[2]][d[3]]) else: self.statut('Déplacment invalide ! (' + test['message'] + ')') return test['valide'] def dClic(self, x, y): """ Réagit en fonction d'un clic sur une case aux coordonnées données """ if not self.logique.partieFinie: if self.dEtape: # Prendre self.dx1, self.dy1 = x, y self.coulDamier() # Effacement des surbrillances if self.logique.aSonTour(self.logique.grille[self.dx1][self.dy1]): # Si possible jouer self.coulCase(self.dx1, self.dy1, 1) self.mvtPossibleSansEchecs = self.logique.mvtsPossibles(self.dx1, self.dy1) # Surbrillance bleue for i in self.mvtPossibleSansEchecs: # Surbrillances vertes self.coulCase(i[0], i[1], 2) self.statut('Cliquez où déposer la pièce.') self.dEtape = not self.dEtape else: # Si pas possible de jouer self.coulCase(self.dx1, self.dy1, 3) self.animerC(self.dx1, self.dy1) else: # Poser self.dx2, self.dy2 = x, y if self.dPiece(self.dx1, self.dy1, self.dx2, self.dy2) or (self.dx1 == self.dx2 and self.dy1 == self.dy2): # Si déplacement fait / Annule dépalcement self.coulDamier() # Effacer Surbrillance self.dEtape = not self.dEtape if self.logique.partieFinie: self.victoire() else: self.statutPrendre() else: # Si mauvais déplacement self.coulCase(self.dx2, self.dy2, 3) self.animerC(self.dx2, self.dy2) def clic(self, event): """ Réagit en fonction d'un clic """ # if event.x in range(0, self.coteCase * self.logique.CASES_COTE) and event.y in range(0, self.coteCase * self.logique.CASES_COTE): self.dClic(event.x // self.coteCase, event.y // self.coteCase) class FenetreTk: """ Fenêtre pour jouer à des jeux sur damier """ PLACEHOLDER_DIMENSIONS = 300 # Coté du canvas vide du début def __init__(self): """ Constructeur de FenetreTk """ self.fen = None # Objet Fenêtre self.can = None # Objet Canvas self.chaine = None # Objet Label de statut self.plateau = None # Plateau de jeu self.creerFen() self.fen.mainloop() def creerFen(self): """ Crée la fenêtre et ses éléments """ self.fen = Tk() self.fen.title("Jeu de plateau") self.can = Canvas(self.fen, width=self.PLACEHOLDER_DIMENSIONS, height=self.PLACEHOLDER_DIMENSIONS, bg="ivory") self.can.grid(row=0, column=1, columnspan=3) self.chaine = Label(self.fen, text="Bienvenue !") self.chaine.grid(row=2, column=2, padx=3, pady=3) Button(self.fen, text="Nv. Partie", command=self.nvPartie).grid(row=2, column=1, padx=3, pady=3) Button(self.fen, text="Quitter", command=self.fen.destroy).grid(row=2, column=3, padx=3, pady=3) def statut(self, texte, delai=0): """ Change le message affiché. """ self.chaine.config(text=texte) # TODO Messages permanents et messages temporaires # (exemple permanent : "Aux blancs de jouer", exemple # temporaire "Vous ne pouvez pas jouer ici !") def nvPartie(self): """ Démarre une nouvelle partie. """ del self.plateau self.plateau = PlateauTk(self.fen, self.can, self.statut, LogiqueEchecs())