Geoffrey Frogeye
6ff94b2079
Au prix d'une animation de promotion inexistante lors de la promotion du pion...
492 lines
18 KiB
Python
492 lines
18 KiB
Python
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('<Button-1>', 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("<Escape>", 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)
|