Case.py 20 KB

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