from tkinter import * from logique import * 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 = 300 # 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 # Fenêtre Tk (nécessaire pour lancer un timeout pour l'animation) self.fen = fen self.statut = statut # Fonction qui envoie un message à l'utilisateur # Tableau contenant les références aux cases du damier self.grilleDamier = [] 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) # Dictionnaire associant chaque pièce à son image originale self.imagesOriginales = {} # Dictionnaire associant chaque pièce à son image redimensionnée self.imagesRedim = {} self.animations = [] # Liste contenant les animations en cours # Étape du déplacement (True : prendre la pièce, False : poser la # pièce) self.dEtape = True 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 # Liste des mouvements possibles pour la pièce sélectionnée self.mvtsPossibles = [] 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.animations = [] 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 importerImage(self, piece): """ Importe l'image de la pièce donnée """ try: nom = 'sprites/' tPiece = self.logique.tPiece(piece) if tPiece == self.logique.PCE_PION: nom += 'pion' elif tPiece == self.logique.PCE_DAME: nom += 'dame' elif tPiece == self.logique.PCE_TOUR: nom += 'tour' elif tPiece == self.logique.PCE_CAVALIER: nom += 'cavalier' elif tPiece == self.logique.PCE_FOU: nom += 'fou' elif tPiece == self.logique.PCE_ROI: nom += 'roi' nom += ('B' if self.logique.ePieceBlanche(piece) else 'N') + '.gif' self.imagesOriginales.update({piece: PhotoImage(file=nom)}) except: return False def importerImages(self): """ Importe les images des pièces du jeu """ for piece in self.logique.BLANCS: self.importerImage(piece) for piece in self.logique.NOIRS: self.importerImage(piece) 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)) 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 """ 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 = [[self.cCase(x, y) for y in range(self.logique.CASES_COTE)] \ for x in range(self.logique.CASES_COTE)] 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 = [[False for y in range(self.logique.CASES_COTE)] \ for x in range(self.logique.CASES_COTE)] 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 """ # x = i['x1'] + (i['x2'] - i['x1']) * (i['avancement'] / i['total']) # x = i['x1'] + (i['x2'] - i['x1']) * (i['avancement'] / i['total']) y = i['y1'] + (i['y2'] - i['y1']) * (-(i['avancement'] / i['total']-1)**2+1) x = i['x1'] + (i['x2'] - i['x1']) * (-(i['avancement'] / i['total']-1)**2+1) 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 # On prévoit une image (boucle) self.fen.after(self.INTER_ANIM, self.animation) 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.update({ 'x1': coords[0], 'y1': coords[1], 'x2': x2, 'y2': y2, # 'total': i['total'] - i['avancement'], 'total': self.TEMPS_ANIM, '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.ePieceNoire(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 # TODO L'ordre de vérification des animations importe beaucoup. Il faudrait trouver un # moyen de rendre ceci moins fragile 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]]) for a in test['ajouter']: # TODO Ajouter une animation self.cPiece(a[0], a[1], a[2]) 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 # Si peut jouer if self.logique.aSonTour(self.logique.grille[self.dx1][self.dy1]): self.coulCase(self.dx1, self.dy1, 1) self.mvtsPossibles = \ self.logique.mvtsPossibles( self.dx1, self.dy1) # Surbrillance bleue for i in self.mvtsPossibles: # 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 ne peut 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 / annulé 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) self.fen.bind("", self.fen.destroy) def statut(self, texte): """ 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 !") @staticmethod def choixNvPartie(): dialogue = Toplevel() dialogue.geometry('+400+400') dialogue.grab_set() dialogue.title('Choix du mode') def fermer(): dialogue.destroy() MODES = [('Échecs', 'Echecs'), ('Dames (International)', 'Dames'), \ ('Dames (Anlaises)', 'DamesAnglaises')] modeChoisi = StringVar() modeChoisi.set(MODES[0][1]) for texte, mode in MODES: Radiobutton(dialogue, text=texte, variable=modeChoisi, value=mode).pack(anchor=W) Button(dialogue, text='Commencer !', command=fermer).pack(anchor=W) dialogue.wait_window(dialogue) return modeChoisi.get() def nvPartie(self): """ Démarre une nouvelle partie. """ del self.plateau choix = self.choixNvPartie() if choix == 'Echecs': logique = LogiqueEchecs() elif choix == 'Dames': logique = LogiqueDames() elif choix == 'DamesAnglaises': logique = LogiqueDamesAnglaises() self.plateau = PlateauTk(self.fen, self.can, self.statut, logique)