#from __future__ import unicode_literals # -*- coding: utf-8 -*- from __future__ import division from PyQt4.QtCore import Qt, SIGNAL, QPointF, QString, QRectF from PyQt4.QtGui import QGraphicsPolygonItem, QColor, QGraphicsItem, \ QGraphicsSimpleTextItem, QBrush, QPen, QRadialGradient, QFont, qRed, \ qGreen, qBlue, QGraphicsEllipseItem, QGraphicsTextItem, QTextBlockFormat, \ QTextCursor import Modes from Pion import Pion from Terrain import Terrain from lib.dmF import DmPile class Case(QGraphicsPolygonItem): """objet graphique representant une case du plateau""" def __init__(self, plateau, parent=None): super(Case, self).__init__(parent) #plateau self.plateau = plateau #attributs self.x = 0 self.y = 0 self.z0 = 0 self.terrain = Terrain() #terrain par defaut self.bordure = QColor(85, 85, 85, 85) #couleur de la bordure par defaut self.centreGraphique = None # self._occ = {} #zA: num du pion self._occ = DmPile() self.caches = [] #liste des caches places par le MJ sur la case (cache le terrain, les decors, les pions aux joueurs...) #effet sur la case self.effetActif = "" #polygones d'affichage self.polygoneEffet = None self.polygoneAffichageSpecial = None self.etiquetteAltitude = None self.polygoneCache = None def __getstate__(self): """selectionne les attributs qui seront sauvegardes""" state = {key:value for key, value in self.__dict__.items() if key in ["x", "y", "z0","terrain","bordure","couleur", \ "ombre", "effetActif", "caches"]} return (state) def __setstate__(self, state): """recupere les attributs sauvegardes""" self.__dict__ = state # if self.z0 == None: self.z0 = 0 ######## fonctions de base : geometrie, aspect graphique ###### def creer(self, x, y, couleur = QColor(0, 255, 0, 80)): """creation du polygone et enregistrement des donnees geometriques""" self.x = x self.y = y self.terrain.couleur = couleur #creation de l'objet graphique sur le plateau self.creerGraphique() def creerGraphique(self): """cree les objets graphiques representant la case""" #reinitialisation des variables self.couleurEffet = {"brule":QColor("orange"), "glace":QColor("white"), "poison":QColor("green"), "eau":QColor("blue")} self.imgEffet = {"brule":"effFeu.jpg", "glace":"effGlace.jpg", "poison":"effPoison.png", "eau":"effEau.png"} #enregistrement des cases voisines: self.voisins = self.lstVoisins(self.x, self.y) # self._occ = {} self._occ = DmPile() #enregistrement du centre if self.plateau.formeCases == "H": #refPlateau k = 0 if 1 == (self.x % 2): k = 0.5 self.centreGraphique = QPointF(((self.x*0.866)+0.5773)*self.plateau.hCase, (self.y+k+0.5)*self.plateau.hCase) else: self.centreGraphique = QPointF((self.x+0.5)*self.plateau.hCase, (self.y+0.5)*self.plateau.hCase) #cree le polygone de la case self.setPolygon(self.polygone()) self.plateau.addItem(self) #refPlateau #interactions graphiques: self.setFlag(QGraphicsItem.ItemIsFocusable) self.setAcceptHoverEvents(True) self.setAcceptDrops(True) self.setZValue(0) self.setFiltersChildEvents(True) # #pour afficher les coordonnees des cases: self.etiquette = None self.afficherEtiquette("{}-{}".format(self.x,self.y)) self.logoDep = LogoDep(self) self.logoDep.creer() #apparence initiale de la case self.majTerrain(self.terrain) #polygone utilise pour afficher la cible du curseur self.maille = Maille(self) self.maille.creer() # self.polygoneCible = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self) # self.polygoneCible = QGraphicsPolygonItem(self.polygone(self.x, self.y)) # self.polygoneCible.setVisible(False) # self.polygoneCible.setAcceptHoverEvents(False) # self.polygoneCible.setZValue(100) # self.plateau.addItem(self.polygoneCible) #cache self.polygoneCache = PolygoneCache(self) self.polygoneCache.creer() def recreer(self, plateau): ## self.plateau = plateau #refPlateau self.plateau = plateau super(Case, self).__init__() #polygones d'affichage self.polygoneEffet = None self.polygoneAffichageSpecial = None self.polygoneCache = None self.etiquetteAltitude = None self.creerGraphique() self.majTerrain(self.terrain) self.majEffet(self.effetActif) def polygone(self): return self.plateau.geo.polygone(self.x, self.y) # def polygone(self, x, y): # """renvoie l'objet graphique hexagone de la case""" # polygone = QPolygonF() # if self.plateau.formeCases == "H": # #si x est impair sur un plateau a cases hexagonales, le y est augmente de 0.5 # if 1 == (x % 2): # y += 0.5 # polygone << QPointF(((x*0.866)+0.2886)*self.plateau.hCase, y*self.plateau.hCase) \ # << QPointF(((x*0.866)+0.866)*self.plateau.hCase, y*self.plateau.hCase) \ # << QPointF(((x*0.866)+1.1547)*self.plateau.hCase, (y+0.5)*self.plateau.hCase) \ # << QPointF(((x*0.866)+0.866)*self.plateau.hCase, (y+1)*self.plateau.hCase) \ # << QPointF(((x*0.866)+0.2886)*self.plateau.hCase, (y+1)*self.plateau.hCase) \ # << QPointF( (x*0.866)*self.plateau.hCase, (y+0.5)*self.plateau.hCase) # else: # polygone << QPointF(x*self.plateau.hCase, y*self.plateau.hCase) \ # << QPointF((x+1)*self.plateau.hCase, y*self.plateau.hCase) \ # << QPointF((x+1)*self.plateau.hCase, (y+1)*self.plateau.hCase) \ # << QPointF(x*self.plateau.hCase, (y+1)*self.plateau.hCase) # return polygone def lstVoisins(self, x, y): """renvoie la liste des cases voisines seulement cases existantes sur le plateau / seulement cases adjacentes (cas des cases carrees)""" voisins = [] if self.plateau.formeCases == "H": if 1 == (x % 2): lst = [(x, y-1), (x+1, y), (x+1, y+1), (x, y+1), (x-1, y+1), (x-1, y)] else: lst = [(x, y-1), (x+1, y-1), (x+1, y), (x, y+1), (x-1, y), (x-1, y-1)] else: lst = [(x, y-1), (x+1, y-1), (x+1, y), (x+1, y+1), (x, y+1), (x-1, y+1), (x-1, y), (x-1, y-1)] for coord in lst: if (coord[0] >= 0 and coord[1] >= 0 and coord[0] < self.plateau.nbCasesX and coord[1] < self.plateau.nbCasesY): voisins.append(coord) return voisins def afficherEtiquette(self, txt): """affiche l'etiquette avec le texte demande""" if self.etiquette == None: self.etiquette = QGraphicsSimpleTextItem(QString().fromUtf8(str(txt)), parent=self) k = 0 if 1 == (self.x % 2): k = 0.5 if self.plateau.formeCases == "H": self.etiquette.setPos(QPointF(((self.x*0.866)+0.2886)*self.plateau.hCase, (self.y+k+0.5)*self.plateau.hCase)) else: self.etiquette.setPos(QPointF(self.x*self.plateau.hCase, self.y*self.plateau.hCase)) else: self.etiquette.setText(QString().fromUtf8(str(txt))) ######################## ### occupation def z0(self): """altitude du terrain nu """ if self.terrain.hPlafond: return None return self.z0 def zP(self): return self.plateau.zP if self.plateau.zP != None else 100 def z1(self): """premiere altitude ABSOLUE disponible altitude z0 de la case, augmentee de la hauteur des decors empiles dessus; None si aucune altitude disponible""" if self.terrain.hPlafond: return None z1 = (self.z0 + self._occ.hauteur()) if z1 >= self.zP(): return None return z1 def occuper(self, num, h): """un pion vient occuper la case a l'altitude relative zR""" if h == 100: h = self.zP() - self.z0 self._occ.ajouter(num, h) def liberer(self, num): """aucun pion n'occupe plus la case a l'altitude relative zR""" self._occ.retirer(num) def occupants(self): """renvoie la liste des occupants de la case""" return self._occ.pile() def occupant(self, zA = None): """renvoie l'occupant de la case a l'altitude ABSOLUE zA: - le numero du pion s'il y en a un - 0 si sous le sol ou au dessus du plafond - sinon None""" #1 - si pas d'altitude, on renvoie l'occupant du dessus s'il existe if not zA: return self._occ.premier() #2 - si au dessous du sol ou au dessus du plafond: if zA < self.z0 or zA >= self.zP(): return 0 #3 - on renvoie l'objet de la pile s'il existe # (!!! attention, la pile fonctionne avec des altitudes relatives) return self._occ[(zA - self.z0)] def estOccupee(self, zA): """renvoie vrai si la case est occupee a l'altitude absolue zA""" return (self.occupant(zA) != None) def occupation(self): return self._occ def zA(self, num): """renvoie l'altitude absolue de l'occupant s'il existe, None sinon""" if not num in self.occupants(): return None return self.z0 + self._occ.zR(num) def hOcc(self, num): """renvoie la liste des altitudes absolues occupees poar le pion s'il existe, une liste vide sinon""" zA = self.zA(num) if not zA != None: return [] return range(zA, (zA + self._occ.hDe(num))) ########## fonctions de maj ########### def majTerrain(self, terrain = Terrain()): """met a jour le terrain de la case""" self.terrain = terrain if self.terrain.couleur.isValid(): self.setBrush(QBrush(self.terrain.couleur)) pinceau = QPen() pinceau.setColor(self.couleurBordure()) pinceau.setWidth(1) self.setPen(pinceau) def majAltitude(self, z0): """met a jour l'altitude de la case""" if z0 > self.zP(): z0 = self.zP() self.z0 = z0 def majAffichageDeplacement(self, cout, valide = True, dz = 0): """lorsque cette case est sur le chemin d'un pion, un disque blanc ou rouge apparait indiquant le cout du deplacement jusqua cette case un cout de 0 fait disparaitre ce symbole""" self.logoDep.afficher(cout, valide, dz) def majEstCibleCurseur(self, actif, valide=True): """affiche la case comme etant la cible du curseur (lors d'une creation de pion, d'un deplacement...)""" self.maille.afficher(actif, valide) # if actif: # pinceau = QPen() # pinceau.setWidth(5) # brush = QBrush() # brush.setStyle(13) # pinceau.setColor(self.couleurProj(valide)) # brush.setColor(self.couleurProj(valide)) # self.polygoneCible.setPen(pinceau) # self.polygoneCible.setBrush(brush) # self.polygoneCible.setVisible(actif) def majEffet(self, effet): """met a jour l'effet actif sur la case""" if self.polygoneEffet == None: #on cree le polygone utilise pour afficher les effets self.polygoneEffet = QGraphicsPolygonItem(self.polygone(), parent=self) pinceau = QPen() pinceau.setColor(self.bordure) pinceau.setWidth(1) self.polygoneEffet.setPen(pinceau) self.polygoneEffet.setVisible(False) if len(effet) > 0 and effet != "aucun": #gradient de couleur gradient = QRadialGradient(self.centreGraphique, 0.5*self.plateau.hCase) couleur0 = QColor(0,0,0,0) couleur20 = self.couleurEffet[effet] couleur20.setAlpha(70) couleur10 = self.couleurEffet[effet] couleur10.setAlpha(255) gradient.setColorAt(0, couleur0) gradient.setColorAt(0.8, couleur0) gradient.setColorAt(0.95, couleur10) gradient.setColorAt(0.98, couleur20) gradient.setColorAt(1, couleur10) self.polygoneEffet.setBrush(gradient) self.polygoneEffet.setVisible(True) self.effetActif = effet else: self.effetActif = "" self.polygoneEffet.setVisible(False) #caches def ajouterCache(self, idCache): if not idCache in self.caches: self.caches.append(idCache) self.majCache() def retirerCache(self, idCache): if idCache in self.caches: self.caches.remove(idCache) self.majCache() def majCache(self): """met a jour l'aspect de la case selon la situation""" self.polygoneCache.afficher((len(self.caches) > 0)) self.polygoneCache.majTransparence() def estCachee(self): """une case cachee ne recoit plus aucun evenement souris ou clavier""" return (len(self.caches) > 0) #fonctions secondaires def couleurDep(self): """renvoie une couleur secondaire utilisee pour les champs de deplacements""" luminositeActuelle = self.couleurEffective().lightness() if luminositeActuelle < 150: retour = QColor(210,255,255,100) else: retour = QColor(210,255,255,100) return retour def couleurProj(self, valide): """renvoie une couleur secondaire utilisee pour les projections de deplacement""" luminositeActuelle = self.couleurEffective().lightness() if valide: retour = QColor("white") else: retour = QColor("red") if luminositeActuelle > 220: retour = retour.darker(120) return retour def couleurBordure(self): """renvoie la couleur utilisee pour les bordures de la case""" luminositeActuelle = self.couleurEffective().lightness() if luminositeActuelle > 150: retour = QColor(85, 85, 85, 130) elif luminositeActuelle > 100 and luminositeActuelle <= 150: retour = QColor(10, 10, 10, 130) else: retour = QColor(255, 255, 255, 180) return retour def couleurEffective(self): """renvoie la couleur effective de la case (utile quand la texture est une image)""" texture = self.brush() if texture.textureImage().isNull(): couleurFond = texture.color() else: couleurFond = self.couleurDominante(texture.textureImage()) return couleurFond def couleurDominante(self, img): """retourne la couleur dominante d'une QImage""" r = 0 v = 0 b = 0 for i in range(0, img.width()): for j in range(0, img.height()): pixel = img.pixel(i,j) r += qRed(pixel) v += qGreen(pixel) b += qBlue(pixel) nb_pix = img.width() * img.height() r_moy = int(r / nb_pix) v_moy = int(v / nb_pix) b_moy = int(b / nb_pix) return QColor(r_moy, v_moy, b_moy) ######### evenements souris et clavier ################# #recuperes depuis la maille #la case recoit d'abord l'evenement #si elle ne l'a pas accepte, les pions le recoivent par ordre d'empilement, def _mousePressEvent(self, event): """evenement lors du clic souris""" # super(Case, self).mousePressEvent(event) if event.button() == 1: if self.plateau.clic_case((self.x, self.y)): return True for num in self.occupants(): if self.plateau.clic_pion(num): return True return False def _hoverEnterEvent(self, event): """met a jour l'affichage de la case au survol de la souris, si un pion est selectionne""" # super(Case, self).hoverEnterEvent(event) if not self.estCachee() or issubclass(self.plateau.modeActif.__class__, Modes.ModeBaseCp): if self.plateau.survol_case((self.x, self.y)): return True for num in self.occupants(): if self.plateau.survol_pion(num): return True return False def _hoverLeaveEvent(self, event): """met a jour l'affichage de la case au survol de la souris, si un pion est selectionne""" # super(Case, self).hoverLeaveEvent(event) if not self.estCachee() or issubclass(self.plateau.modeActif.__class__, Modes.ModeBaseCp): if self.plateau.finSurvol_case((self.x, self.y)): return True for num in self.occupants(): if self.plateau.finSurvol_pion(num): return True return False def _mouseDoubleClickEvent(self, event): """evenement lors du double-clic souris""" super(Pion, self).mouseDoubleClickEvent(event) if event.button() == 1: #sur clic gauche if self.plateau.doubleClic_pion(self.numero): return True return False ######################## class Maille(QGraphicsPolygonItem): """clone de la case place au dessus des autres items, et permettant aux cases de recevoir prioritairement certains signaux, meme lorsqu'elles sont cachees sous des pions, caches...Etc""" def __init__(self, case, parent = None): super(Maille, self).__init__() self._case = case self._actif = False def coord(self): return (self._case.x, self._case.y) def paint(self, *args): if not self._actif: return return super(Maille, self).paint(*args) def creer(self): self.setPolygon(self._case.polygone()) # self.setVisible(False) self.setAcceptHoverEvents(True) self.setZValue(100) self._case.plateau.addItem(self) self.afficher(False) def afficher(self, actif, valide = True): self._actif = actif pinceau = QPen() brush = QBrush() if actif: pinceau.setWidth(5) pinceau.setColor(self._case.couleurProj(valide)) brush.setStyle(13) brush.setColor(self._case.couleurProj(valide)) self.setPen(pinceau) self.setBrush(brush) def mousePressEvent(self, event): """evenement lors du clic souris""" accepte = self._case._mousePressEvent(event) if accepte: event.accept() else: event.ignore() super(Maille, self).mousePressEvent(event) def hoverEnterEvent(self, event): """met a jour l'affichage de la case au survol de la souris, si un pion est selectionne""" accepte = self._case._hoverEnterEvent(event) if accepte: event.accept() else: event.ignore() super(Maille, self).hoverEnterEvent(event) def hoverLeaveEvent(self, event): """met a jour l'affichage de la case au survol de la souris, si un pion est selectionne""" accepte = self._case._hoverLeaveEvent(event) if accepte: event.accept() else: event.ignore() super(Maille, self).hoverLeaveEvent(event) class LogoDep(QGraphicsEllipseItem): """logo utilise pour afficher le chemin que va suivre le pion lors d'un deplacement""" def __init__(self, parent = None): super(LogoDep, self).__init__() self.parent = parent self.couleur = None self.txt = None def creer(self): hC = self.parent.plateau.hCase rect = QRectF() rayon = 0.4*hC rect.setTopLeft(QPointF(self.parent.centreGraphique.x() - rayon, self.parent.centreGraphique.y() - rayon)) rect.setBottomRight(QPointF(self.parent.centreGraphique.x() + rayon, self.parent.centreGraphique.y() + rayon)) self.setRect(rect) self.txt = QGraphicsTextItem(self) police = QFont("Georgia", 24) police.setBold(True) self.txt.setFont(police) self.txt.setParentItem(self) self.txt.setPos(QPointF(self.parent.centreGraphique.x() - rayon, self.parent.centreGraphique.y() - 0.6*rayon)) self.txt.setTextWidth(2*rayon) self.txt.setPlainText(QString.fromUtf8("")) self.centrerTexte() self.setVisible(False) self.setZValue(100) self.setAcceptHoverEvents(False) self.parent.plateau.addItem(self) def afficher(self, valeur, valide, dz = 0): """lorsque cette case est sur le chemin d'un pion, un disque blanc ou rouge apparait indiquant le mode de deplacement (icone: 'dep_simple', 'nage', 'escalade', 'vol') un cout de 0 fait disparaitre ce symbole""" if valeur > 0: #couleur if valide: couleur = QColor(255, 255, 220, 200) else: couleur = QColor(240, 60, 20, 200) self.setBrush(couleur) self.setPen(QPen(couleur)) #txt escalade = "" if dz == 0 else (chr(30) if dz > 0 else chr(31)) txt = "{}{}".format(valeur, escalade) self.txt.setPlainText(QString.fromUtf8(txt)) self.centrerTexte() self.setVisible(True) else: self.setVisible(False) def centrerTexte(self): f = QTextBlockFormat() f.setAlignment(Qt.AlignCenter) cursor = self.txt.textCursor() cursor.select(QTextCursor.Document) cursor.mergeBlockFormat(f) cursor.clearSelection() self.txt.setTextCursor(cursor) class PolygoneCache(QGraphicsPolygonItem): def __init__(self, case, parent=None): """initialisation de la fenetre""" super(PolygoneCache, self).__init__(parent) self.case = case self.case.plateau.fenetre.connect(self.case.plateau.fenetre.ui.cbt_vue, SIGNAL("zoomChange(int)"), self.zoomChange) def creer(self): polygone = self.case.polygone() self.setPolygon(polygone) self.majTransparence() self.setTransformOriginPoint(self.case.centreGraphique) self.setZValue(98) self.setVisible(False) self.setScale(1.05) # self.setAcceptHoverEvents(True) # self.setAcceptDrops(True) self.case.plateau.addItem(self) def afficher(self, actif = True): self.setVisible(actif) def majTransparence(self): self.setBrush(QColor(0,0,0,self.transparence())) pinceau = QPen() pinceau.setBrush(QColor(0,0,0,self.transparence())) pinceau.setWidth(1) self.setPen(pinceau) def transparence(self): if issubclass(self.case.plateau.modeActif.__class__, Modes.ModeBaseCp): retour = 100 else: retour = 255 return retour def activerSurbrillance(self, actif): if actif: couleur = QColor(150,150,150,100) else: couleur = QColor(0,0,0,100) self.setBrush(couleur) def zoomChange(self, nbZoom): """le zoom sur la dmGraphicsView a change, on met a jour le scale de l'objet, on cherche a eviter un phenomene qui fait que lorsque le zoom est lointain, on devine les couleurs entre les polygones du cache, le rendant a moitie inutile""" s = (1 + 0.01*(10-nbZoom)) self.setScale(s) def sceneEvent(self, event): """filtre les evenements souris et clavier si faux: l'evenement est bloque""" autorise = not self.case.estCachee() or issubclass(self.case.plateau.modeActif.__class__, Modes.ModeBaseCp) if not autorise: event.accept() else: event.ignore() return autorise