Case.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #from __future__ import unicode_literals
  2. # -*- coding: utf-8 -*-
  3. from __future__ import division
  4. import os
  5. from PyQt4.QtCore import *
  6. from PyQt4.QtGui import *
  7. from outilsSvg import *
  8. from Terrain import Terrain
  9. class Case(QGraphicsPolygonItem):
  10. """objet graphique representant une case du plateau"""
  11. def __init__(self, plateau, parent=None):
  12. super(Case, self).__init__(parent)
  13. #plateau
  14. self.plateau = plateau
  15. self.dimPlateauX = self.plateau.nbCasesX
  16. self.dimPlateauY = self.plateau.nbCasesY
  17. self.formeCases = self.plateau.formeCases
  18. self.hCase = self.plateau.hCase
  19. #attributs
  20. self.x = 0
  21. self.y = 0
  22. self.altitude = 0
  23. self.terrain = Terrain() #terrain par defaut
  24. self.couleur = None #couleur du fond par defaut
  25. self.bordure = QColor(85, 85, 85, 85) #couleur de la bordure par defaut
  26. self.estDansChampDeplacement = False #la case est dans le champ de deplacement du pion selectionne
  27. self.centreGraphique = None
  28. self.formeCases = "H"
  29. self.occupeePar = {} #objet: altitudes occupees (sous forme de tuple, ex: (0,1,2) pour une creature occupant les cases d'altitude 0, 1 et 2)
  30. self.estCache = False #est sous un cache place par le MJ (cache le terrain, les decors, les pions aux joueurs...)
  31. self.ombre = True #ombre (cache les pions uniquement)
  32. #effet sur la case
  33. self.effetActif = ""
  34. #polygones d'affichage
  35. self.polygoneEffet = None
  36. self.polygoneAffichageSpecial = None
  37. self.etiquetteAltitude = None
  38. def __getstate__(self):
  39. """selectionne les attributs qui seront sauvegardes"""
  40. self.idTerrain = self.terrain.id
  41. state = {key:value for key, value in self.__dict__.items() if key in ["x", "y", "altitude","idTerrain","bordure","couleur", \
  42. "estCache", "ombre", "effetActif"]}
  43. return (state)
  44. def __setstate__(self, state):
  45. """recupere les attributs sauvegardes"""
  46. self.__dict__ = state
  47. if self.idTerrain == "00":
  48. self.terrain = Terrain()
  49. else:
  50. self.terrain = charger("librairie\\terrain", self.idTerrain)
  51. ######## fonctions de base : geometrie, aspect graphique ######
  52. def creer(self, x, y, couleur = QColor(0, 255, 0, 80)):
  53. """creation du polygone et enregistrement des donnees geometriques"""
  54. self.x = x
  55. self.y = y
  56. self.couleur = couleur
  57. #creation de l'objet graphique sur le plateau
  58. self.creerGraphique()
  59. def creerGraphique(self):
  60. """cree les objets graphiques representant la case"""
  61. #reinitialisation des variables
  62. self.couleurEffet = {"brule":QColor("orange"), "glace":QColor("white"), "poison":QColor("green"), "eau":QColor("blue")}
  63. self.imgEffet = {"brule":"effFeu.jpg", "glace":"effGlace.jpg", "poison":"effPoison.png", "eau":"effEau.png"}
  64. #enregistrement des cases voisines:
  65. self.voisins = self.lstVoisins(self.x, self.y)
  66. self.occupeePar = {}
  67. #enregistrement du centre
  68. if self.formeCases == "H": #refPlateau
  69. self.centreGraphique = QPointF(((self.x*0.866)+0.5773)*self.hCase, (self.y+0.5)*self.hCase)
  70. else:
  71. self.centreGraphique = QPointF((self.x+0.5)*self.hCase, (self.y+0.5)*self.hCase)
  72. #cree le polygone de la case
  73. self.setPolygon(self.polygone(self.x, self.y))
  74. self.plateau.addItem(self) #refPlateau
  75. #interactions graphiques:
  76. self.setFlag(QGraphicsItem.ItemIsFocusable)
  77. self.setAcceptHoverEvents(True)
  78. self.setAcceptDrops(True)
  79. self.setZValue(0)
  80. self.setFiltersChildEvents(True)
  81. #pour afficher les coordonnees des cases:
  82. #text = QGraphicsSimpleTextItem("{}-{}".format(self.x,self.y), parent=self)
  83. #if self.plateau.formeCases == "H":
  84. # text.setPos(QPointF(((self.x*0.866)+0.2886)*self.plateau.hCase, self.y*self.plateau.hCase))
  85. #else:
  86. # text.setPos(QPointF(self.x*self.plateau.hCase, self.y*self.plateau.hCase))
  87. #polygone utilise lorsque dans champ de deplacement
  88. self.polygoneChampDep = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self)
  89. self.polygoneChampDep.setVisible(False)
  90. self.polygoneChampDep.setAcceptHoverEvents(False)
  91. #apparence initiale de la case
  92. self.majCouleur(self.couleur)
  93. #polygone utilise pour afficher la cible du curseur
  94. self.polygoneCible = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self)
  95. self.polygoneCible.setVisible(False)
  96. self.polygoneCible.setAcceptHoverEvents(False)
  97. #polygone test (pour capter les evts souris par dessus d'autres objets)
  98. ## self.polygoneTest = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self)
  99. ## self.polygoneTest.setVisible(True)
  100. ## self.polygoneTest.setAcceptHoverEvents(True)
  101. ## self.polygoneTest.setFlag(QGraphicsItem.ItemIsFocusable)
  102. ## self.polygoneTest.setBrush(QColor(0,0,0,0))
  103. ## self.polygoneTest.setPen(QPen(QColor(0,0,0,0)))
  104. ## self.polygoneTest.setAcceptedMouseButtons(Qt.NoButton)
  105. ## self.polygoneTest.setZValue(200)
  106. ## self.polygoneTest.setParentItem(self)
  107. def majGraphique(self):
  108. """met a jour l'aspect graphique de la acse en fonction de son etat"""
  109. if self.terrain:
  110. if len(self.terrain.nom) > 0 :
  111. self.majTerrain(self.terrain)
  112. self.majAffichageSpecial("")
  113. self.majEffet(self.effetActif)
  114. def recreer(self, plateau):
  115. ## self.plateau = plateau #refPlateau
  116. super(Case, self).__init__()
  117. #polygones d'affichage
  118. self.polygoneEffet = None
  119. self.polygoneAffichageSpecial = None
  120. self.etiquetteAltitude = None
  121. self.creerGraphique()
  122. self.majGraphique()
  123. def polygone(self, x, y):
  124. """renvoie l'objet graphique hexagone de la case"""
  125. polygone = QPolygonF()
  126. if self.formeCases == "H":
  127. polygone << QPointF(((x*0.866)+0.2886)*self.hCase, y*self.hCase) \
  128. << QPointF(((x*0.866)+0.866)*self.hCase, y*self.hCase) \
  129. << QPointF(((x*0.866)+1.1547)*self.hCase, (y+0.5)*self.hCase) \
  130. << QPointF(((x*0.866)+0.866)*self.hCase, (y+1)*self.hCase) \
  131. << QPointF(((x*0.866)+0.2886)*self.hCase, (y+1)*self.hCase) \
  132. << QPointF( (x*0.866)*self.hCase, (y+0.5)*self.hCase)
  133. else:
  134. polygone << QPointF(x*self.hCase, y*self.hCase) \
  135. << QPointF((x+1)*self.hCase, y*self.hCase) \
  136. << QPointF((x+1)*self.hCase, (y+1)*self.hCase) \
  137. << QPointF(x*self.hCase, (y+1)*self.hCase)
  138. return polygone
  139. def lstVoisins(self, x, y):
  140. """renvoie la liste des cases voisines
  141. seulement cases existantes sur le plateau / seulement cases adjacentes (cas des cases carrees)"""
  142. voisins = []
  143. if self.formeCases == "H":
  144. lst = [(x, y-1), (x+1, y-0.5), (x+1, y+0.5), (x, y+1), (x-1, y+0.5), (x-1, y-0.5)]
  145. else:
  146. 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)]
  147. for coord in lst:
  148. if (coord[0] >= 0 and coord[1] >= 0 and coord[0] < self.dimPlateauX and coord[1] < self.dimPlateauY):
  149. voisins.append(coord)
  150. return voisins
  151. ########################
  152. ########## fonctions liees a l'etat de la case ###########
  153. def majCouleur(self, couleur):
  154. self.couleur = couleur
  155. self.setBrush(couleur)
  156. self.polygoneChampDep.setBrush(self.couleurDep())
  157. pinceau = QPen()
  158. pinceau.setColor(self.couleurBordure())
  159. pinceau.setWidth(1)
  160. self.setPen(pinceau)
  161. def majTerrain(self, terrain):
  162. if terrain != None:
  163. self.terrain = terrain
  164. if len(self.terrain.imgTexture) > 0:
  165. self.setBrush(QBrush(QImage("img\\"+self.terrain.imgTexture)))
  166. else:
  167. if self.terrain.couleur.isValid():
  168. self.setBrush(QBrush(self.terrain.couleur))
  169. self.couleur = self.terrain.couleur
  170. self.polygoneChampDep.setBrush(self.couleurDep())
  171. else:
  172. self.terrain = Terrain()
  173. def majOccupation(self, objet, nouveauZ = None):
  174. """met a jour l'occupation de la case par les pions, decors..."""
  175. if objet != None:
  176. if not objet in self.occupeePar and nouveauZ != None:
  177. #on ajoute l'objet a la liste des objets occupant la case ou on met a jour son altitude
  178. casesOccupees = []
  179. for i in range(0, objet.hauteur):
  180. casesOccupees.append(self.altitude + nouveauZ + i)
  181. self.occupeePar[objet] = casesOccupees
  182. elif objet in self.occupeePar and nouveauZ == None:
  183. #on supprime l'objet de la liste des objets occupant la case
  184. del self.occupeePar[objet]
  185. else:
  186. pass
  187. def estOccupee(self, z=0):
  188. """renvoie vrai si la case correspondant a la hauteur z est occupee"""
  189. retour = False
  190. for objet in self.occupeePar:
  191. if objet.hauteur > 0 and z in self.occupeePar[objet]:
  192. retour = True
  193. return retour
  194. def estOccupeePar(self, z=0):
  195. """si la case correspondant a la hauteur z est occupee, renvoie l'objet en question
  196. sinon renvoie None"""
  197. retour = None
  198. for objet in self.occupeePar:
  199. if objet.hauteur > 0 and z in self.occupeePar[objet]:
  200. retour = objet
  201. return retour
  202. def pionOccupant(self):
  203. """si un pion occupe cette case, le renvoie"""
  204. retour = None
  205. for objet in self.occupeePar:
  206. if objet.__class__.__name__ == "Pion":
  207. retour = objet
  208. return retour
  209. def estFranchissable(self, z=0):
  210. """la case est-elle franchissable?"""
  211. retour = True
  212. if self.terrain.franchissable == False:
  213. retour = False
  214. else:
  215. if self.estOccupee(z) == True:
  216. retour = False
  217. return retour
  218. def estObstacleVision(self, hauteurObs):
  219. """renvoie vrai si la case et l'eventuel decor qui l'occupe bloquent le champ de
  220. vision d'un observateur situe a la hauteur precisee"""
  221. retour = False
  222. if self.altitude > hauteurObs:
  223. retour = True
  224. else:
  225. hauteurObstacle = 0
  226. for objet in self.occupeePar:
  227. if objet.hauteur > hauteurObstacle:
  228. hauteurObstacle = objet.hauteur
  229. if (self.altitude + hauteurObstacle) > hauteurObs:
  230. retour = True
  231. return retour
  232. def majEstDansChampDeplacement(self, actif):
  233. """la case apparait ou pas comme etant dans le champ de deplacement d'un pion"""
  234. self.polygoneChampDep.setVisible(actif)
  235. def majEstCibleAttaque(self, actif):
  236. """la case apparait ou pas comme etant dans le champ de deplacement d'un pion"""
  237. self.polygoneChampDep.setVisible(actif)
  238. def majEstCibleCurseur(self, actif, valide=True):
  239. """affiche la case comme etant la cible du curseur (lors d'une creation de pion, d'un deplacement...)"""
  240. if actif:
  241. pinceau = QPen()
  242. pinceau.setWidth(5)
  243. brush = QBrush()
  244. brush.setStyle(13)
  245. pinceau.setColor(self.couleurProj(valide))
  246. brush.setColor(self.couleurProj(valide))
  247. self.polygoneCible.setPen(pinceau)
  248. self.polygoneCible.setBrush(brush)
  249. self.setZValue(99)
  250. else:
  251. self.setZValue(0)
  252. self.polygoneCible.setVisible(actif)
  253. def majEffet(self, effet):
  254. """met a jour l'effet actif sur la case"""
  255. if self.polygoneEffet == None:
  256. #on cree le polygone utilise pour afficher les effets
  257. self.polygoneEffet = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self)
  258. pinceau = QPen()
  259. pinceau.setColor(self.bordure)
  260. pinceau.setWidth(1)
  261. self.polygoneEffet.setPen(pinceau)
  262. self.polygoneEffet.setVisible(False)
  263. if len(effet) > 0 and effet != "aucun":
  264. #gradient de couleur
  265. gradient = QRadialGradient(self.centreGraphique, 0.5*self.hCase)
  266. couleur0 = QColor(0,0,0,0)
  267. couleur20 = self.couleurEffet[effet]
  268. couleur20.setAlpha(70)
  269. couleur10 = self.couleurEffet[effet]
  270. couleur10.setAlpha(255)
  271. gradient.setColorAt(0, couleur0)
  272. gradient.setColorAt(0.8, couleur0)
  273. gradient.setColorAt(0.95, couleur10)
  274. gradient.setColorAt(0.98, couleur20)
  275. gradient.setColorAt(1, couleur10)
  276. self.polygoneEffet.setBrush(gradient)
  277. self.polygoneEffet.setVisible(True)
  278. self.effetActif = effet
  279. else:
  280. self.polygoneEffet.setVisible(False)
  281. def majAltitude(self, altitude):
  282. """met a jour l'altitude de la case"""
  283. self.altitude = altitude
  284. def majAffichageSpecial(self, affichage=""):
  285. """donne a la case l'aspect demande en rendant visible/invisible le polygone d'affichage special"""
  286. #polygone d'affichage special (altitude, tactique...)
  287. if self.polygoneAffichageSpecial == None:
  288. self.polygoneAffichageSpecial = QGraphicsPolygonItem(self.polygone(self.x, self.y), parent=self)
  289. pinceau = QPen()
  290. pinceau.setColor(self.bordure)
  291. pinceau.setWidth(1)
  292. self.polygoneAffichageSpecial.setPen(pinceau)
  293. if affichage != "altitude" and self.etiquetteAltitude:
  294. self.etiquetteAltitude.setVisible(False)
  295. if affichage == "tactique":
  296. if self.terrain.franchissable:
  297. self.polygoneAffichageSpecial.setBrush(QColor(255,255,255))
  298. else:
  299. self.polygoneAffichageSpecial.setBrush(QColor(50,50,50))
  300. self.polygoneAffichageSpecial.setVisible(True)
  301. elif affichage == "altitude":
  302. if self.etiquetteAltitude == None:
  303. self.etiquetteAltitude = QGraphicsSimpleTextItem("{}".format(self.altitude), parent=self)
  304. police = QFont("Georgia", 18)
  305. police.setItalic(True)
  306. self.etiquetteAltitude.setFont(police)
  307. self.etiquetteAltitude.setPos(QPointF(((self.x*0.866)+0.65)*self.hCase, (self.y+0.7)*self.hCase))
  308. if self.altitude >= 0:
  309. couleur = QColor("red").lighter(200-(5*self.altitude))
  310. else:
  311. couleur = QColor("purple").lighter(200+(5*self.altitude))
  312. self.polygoneAffichageSpecial.setBrush(couleur)
  313. self.polygoneAffichageSpecial.setZValue(5)
  314. self.polygoneAffichageSpecial.setVisible(True)
  315. self.etiquetteAltitude.setText(QString.fromUtf8("{}".format(self.altitude)))
  316. self.etiquetteAltitude.setVisible(True)
  317. self.etiquetteAltitude.setZValue(6)
  318. else:
  319. self.polygoneAffichageSpecial.setVisible(False)
  320. def couleurDep(self):
  321. """renvoie une couleur secondaire utilisee pour les champs de deplacements"""
  322. luminositeActuelle = self.couleurEffective().lightness()
  323. if luminositeActuelle < 150:
  324. retour = QColor(210,255,255,100)
  325. else:
  326. retour = QColor(210,255,255,100)
  327. return retour
  328. def couleurProj(self, valide):
  329. """renvoie une couleur secondaire utilisee pour les projections de deplacement"""
  330. luminositeActuelle = self.couleurEffective().lightness()
  331. if valide:
  332. retour = QColor("white")
  333. else:
  334. retour = QColor("red")
  335. if luminositeActuelle > 220:
  336. retour = retour.darker(120)
  337. return retour
  338. def couleurBordure(self):
  339. """renvoie la couleur utilisee pour les bordures de la case"""
  340. luminositeActuelle = self.couleurEffective().lightness()
  341. if luminositeActuelle > 150:
  342. retour = QColor(85, 85, 85, 130)
  343. elif luminositeActuelle > 100 and luminositeActuelle <= 150:
  344. retour = QColor(10, 10, 10, 130)
  345. else:
  346. retour = QColor(255, 255, 255, 180)
  347. return retour
  348. def couleurEffective(self):
  349. """renvoie la couleur effective de la case (utile quand la texture est une image)"""
  350. texture = self.brush()
  351. if texture.textureImage().isNull():
  352. couleurFond = texture.color()
  353. else:
  354. couleurFond = self.couleurDominante(texture.textureImage())
  355. return couleurFond
  356. def couleurDominante(self, img):
  357. """retourne la couleur dominante d'une QImage"""
  358. r = 0
  359. v = 0
  360. b = 0
  361. for i in range(0, img.width()):
  362. for j in range(0, img.height()):
  363. pixel = img.pixel(i,j)
  364. r += qRed(pixel)
  365. v += qGreen(pixel)
  366. b += qBlue(pixel)
  367. nb_pix = img.width() * img.height()
  368. r_moy = int(r / nb_pix)
  369. v_moy = int(v / nb_pix)
  370. b_moy = int(b / nb_pix)
  371. return QColor(r_moy, v_moy, b_moy)
  372. ######### evebements souris et clavier #################
  373. #*EC
  374. def mousePressEvent(self, event):
  375. """evenement lors du clic souris"""
  376. super(Case, self).mousePressEvent(event)
  377. if event.button() == 1: #sur clic gauche
  378. accepte = self.plateau.caseCliquee(self.x, self.y) #refPlateau
  379. if accepte: event.accept()
  380. else:
  381. event.ignore()
  382. def hoverEnterEvent(self, event):
  383. """met a jour l'affichage de la case au survol de la souris, si un pion est selectionne"""
  384. super(Case, self).hoverEnterEvent(event)
  385. self.plateau.caseSurvol(self.x, self.y)
  386. def hoverLeaveEvent(self, event):
  387. """met a jour l'affichage de la case au survol de la souris, si un pion est selectionne"""
  388. super(Case, self).hoverLeaveEvent(event)
  389. #self.plateau.caseSurvol()
  390. ########################