Coverage for rollnjump/utilities.py : 100%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Roll 'n' Jump
2# Written in 2020, 2021 by Samuel Arsac, Hugo Buscemi,
3# Matteo Chencerel, Rida Lali
4# To the extent possible under law, the author(s) have dedicated all
5# copyright and related and neighboring rights to this software to the
6# public domain worldwide. This software is distributed without any warranty.
7# You should have received a copy of the CC0 Public Domain Dedication along
8# with this software. If not, see
9# <http://creativecommons.org/publicdomain/zero/1.0/>.
11"""Gère l'abstraction de pygame."""
13import re
14from math import ceil
16import sys
17import pygame
18import rollnjump.conf as cf
20pygame.init()
22# Classes
23Vec = pygame.math.Vector2
24"""Classe des vecteurs de dimension 2."""
25Sprite = pygame.sprite.Sprite
26"""Classe des sprites."""
28# Les touches
29KEYDOWN = pygame.KEYDOWN
30"""Événement "touche enfoncée"."""
31K_ESCAPE = pygame.K_ESCAPE
32"""Touche échap."""
33K_SPACE = pygame.K_SPACE
34"""Touche espace."""
35K_RETURN = pygame.K_RETURN
36"""Touche entrée."""
37K_s = pygame.K_s
38"""Touche S."""
39K_u = pygame.K_u
40"""Touche U."""
41K_BACKSPACE = pygame.K_BACKSPACE
42"""Touche retour."""
43K_ESCAPE = pygame.K_ESCAPE
44"""Touche échap"""
45MOUSEBUTTONDOWN = pygame.MOUSEBUTTONDOWN
46"""Événement "clic de la souris"."""
47VIDEORESIZE = pygame.VIDEORESIZE
48"""Événement "redimensionnement de la fenêtre"."""
49QUIT = pygame.QUIT
50"""Événement "quitter le jeu"."""
53def keyname(key): # pragma: no cover
54 """
55 Renvoie le nom de la touche pressée.
57 Parameters
58 ----------
59 key : Key
60 Touche pressée
62 Returns
63 -------
64 str
65 Nom de la touche
66 """
67 return pygame.key.name(key)
70def keyidentifier(control): # pragma: no cover
71 """
72 Renvoie la touche à partir d'une chaîne de caractères.
74 Parameters
75 ----------
76 control : str
77 Texte caractérisant la touche
79 Returns
80 -------
81 Key
82 Variable de la touche
83 """
84 return pygame.key.key_code(control)
87def load_image(path): # pragma: no cover
88 """
89 Charge une image à partir d'un chemin.
91 Parameters
92 ----------
93 path : str
94 Chemin du fichier
96 Returns
97 -------
98 Surface
99 Image du fichier
100 """
101 return pygame.image.load(path)
104def load_music(path): # pragma: no cover
105 """
106 Charge une musique à partir d'un chemin.
108 Parameters
109 ----------
110 path : str
111 Chemin du fichier
112 """
113 pygame.mixer.music.load(path)
116def play_music(): # pragma: no cover
117 """
118 Lance la musique chargée avec load_music.
120 Boucle automatiquement à la fin du fichier.
121 """
122 pygame.mixer.music.play(-1)
125def pause_music(): # pragma: no cover
126 """Met sur pause la musique chargée avec load_music."""
127 pygame.mixer.music.pause()
130def unpause_music(): # pragma: no cover
131 """Relance la musique chargée avec load_music."""
132 pygame.mixer.music.unpause()
135def initialize_window(icon, title, width,
136 height, graphical): # pragma: no cover
137 """
138 Initialise l'environnement graphique et la fenêtre.
140 Parameters
141 ----------
142 icon : Surface
143 Icone de la fenêtre
144 title : str
145 Nom de la fenêtre
146 width : int
147 Largeur de la fenêtre
148 height : int
149 Hauteur de la fenêtre
150 graphical : bool
151 Indique si la fenêtre doit être affichée
153 Returns
154 -------
155 Surface * Surface
156 Un couple (surface de jeu, surface à afficher)
157 """
158 game = pygame.Surface((width, height))
159 if graphical:
160 pygame.display.set_icon(load_image(icon))
161 pygame.display.set_caption(title)
162 return (game,
163 pygame.display.set_mode((width, height),
164 flags=pygame.RESIZABLE))
165 return (game, None)
168def resize_window(screen_size): # pragma: no cover
169 """
170 Rétablit le ratio après un redimensionnement de fenêtre.
172 Parameters
173 ----------
174 screen_size : int * int
175 Taille de la fenêtre, au ratio quelconque
176 """
177 ratio = min(screen_size[0] / cf.SCREEN_WIDTH,
178 screen_size[1] / cf.SCREEN_HEIGHT)
179 new_screen_size = (ceil(ratio * cf.SCREEN_WIDTH),
180 ceil(ratio * cf.SCREEN_HEIGHT))
181 cf.WINDOWSURF = pygame.display.set_mode(new_screen_size,
182 flags=pygame.RESIZABLE)
185def initialize_clock(): # pragma: no cover
186 """
187 Initialise le temps.
189 Returns
190 -------
191 Clock
192 Une horloge
193 """
194 return pygame.time.Clock()
197def make_event(event_type, attr=None): # pragma: no cover
198 """
199 Renvoie un événement du type passé en entrée.
201 Parameters
202 ----------
203 event_type : int
204 Le type de l'événement
205 attr : dict, optionnel
206 Le dictionnaire des attributs
208 Returns
209 -------
210 Event
211 L'événement correspondant
212 """
213 if attr is None:
214 attr = {}
215 return pygame.event.Event(event_type, attr)
218def get_events(): # pragma: no cover
219 """
220 Renvoie la liste des évènements.
222 Returns
223 -------
224 Event list
225 Liste des évènements
226 """
227 return pygame.event.get()
230def group_sprite_define(): # pragma: no cover
231 """
232 Création d'un nouveau groupe de sprites.
234 Returns
235 -------
236 Group
237 le groupe de sprites
238 """
239 return pygame.sprite.Group()
242def add_to_group(sprite, group): # pragma: no cover
243 """
244 Ajoute un sprite à un groupe de sprites.
246 Parameters
247 ----------
248 sprite : Sprite
249 Le sprite à ajouter
250 group : Group
251 Le groupe de sprites
252 """
253 group.add(sprite)
256def resize(surface, dimensions, destination=None): # pragma: no cover
257 """
258 Change l'échelle de la surface en entrée.
260 Parameters
261 ----------
262 surface : Surface
263 La surface à modifier
264 dimensions : int * int
265 Les nouvelles dimensions
266 destination : Surface, optionnel
267 Nouvel objet à créer pour le redimensionnement
269 Returns
270 -------
271 Surface
272 Surface redimensionnée
273 """
274 if destination is None:
275 return pygame.transform.scale(surface, dimensions)
276 return pygame.transform.scale(surface, dimensions, destination)
279def resize_list(L, size): # pragma: no cover
280 """
281 Redimensionne les images d'une liste.
283 Parameters
284 ----------
285 L : Surface list
286 Liste d'images
287 size : int * int
288 La résolution attendue
290 Returns
291 -------
292 Surface list
293 La liste des images redimensionnées
294 """
295 for i, img in enumerate(L):
296 L[i] = pygame.transform.scale(img, size)
299def contact(sprite1, sprite2): # pragma: no cover
300 """
301 Indique si deux sprites sont en contact.
303 Parameters
304 ----------
305 sprite1 : Sprite
306 Le premier sprite
307 sprite2 : Sprite
308 Le second sprite
310 Returns
311 -------
312 bool
313 True si les deux sprites sont en contact
314 """
315 return pygame.sprite.collide_rect(sprite1, sprite2)
318def collide_group(sprite, group): # pragma: no cover
319 """
320 Indique s'il y a une collision entre un sprite et un groupe de sprites.
322 Parameters
323 ----------
324 sprite : Sprite
325 Le sprite examiné
326 group : Sprite group
327 Le groupe de sprites examiné
329 Returns
330 -------
331 bool
332 True s'il y a une collision
333 """
334 return pygame.sprite.spritecollideany(sprite, group)
337def update_screen(): # pragma: no cover
338 """Met à jour l'écran."""
339 pygame.display.flip()
342def get_screen_size(): # pragma: no cover
343 """
344 Renvoie la taille de la fenêtre.
346 Returns
347 -------
348 int * int
349 Dimensions de la fenêtre
350 """
351 return pygame.display.get_surface().get_size()
354def create_rect(array): # pragma: no cover
355 """
356 Crée un objet rectangle.
358 Parameters
359 ----------
360 array : int list
361 liste contenant l'abscisse de la gauche du rectangle,
362 l'ordonnée du haut du rectangle,
363 sa largeur et sa hauteur
365 Returns
366 -------
367 Rect
368 Le rectangle correspondant
369 """
370 return pygame.Rect(array)
373def draw_rect(surface, color, rect): # pragma: no cover
374 """
375 Dessine l'objet rectangle sur une surface.
377 Parameters
378 ----------
379 surface : Surface
380 La surface sur laquelle afficher le rectangle
381 color : int * int * int
382 La couleur (en RGB)
383 rect : Rect
384 Le rectangle à afficher
385 """
386 pygame.draw.rect(surface, color, rect)
389def mouse_pos(): # pragma: no cover
390 """
391 Renvoie la position de la souris.
393 Returns
394 -------
395 int * int
396 La position du pointeur
397 """
398 return pygame.mouse.get_pos()
401def quit_game(): # pragma: no cover
402 """Quitte le jeu."""
403 pygame.quit()
404 sys.exit()
407def font(font_name, size): # pragma: no cover
408 """
409 Renvoie une fonte de la taille demandée.
411 Parameters
412 ----------
413 font_name : str
414 La police de caractères
415 size : int
416 La taille de la fonte
418 Returns
419 -------
420 Font
421 La fonte
422 """
423 return pygame.font.Font(font_name, size)
426def collide(obj, pos_next, rect):
427 """
428 Gestion des collisions.
430 Parameters
431 ----------
432 obj : Player / Item
433 objet (joueur ou objet) dont on examine la collision
434 pos_next : Vector2
435 position suivante de l'objet
436 rect : Rect
437 ce qui est potentiellement en collision avec l'objet
439 Returns
440 -------
441 bool * bool * Vector2
442 un triplet (collision verticale, collision horizontale,
443 modification de position necessaire)
444 """
445 # On ne tient pas compte du cas dans lequel l'objet traverserait
446 # une plateforme dans sa longueur entre deux positions, il ne serait
447 # de toutes façons pas possible de jouer dans ce cas.
448 pos_prev = obj.pos
450 if pos_next.x + obj.width > rect.left\
451 and pos_next.x < rect.right:
452 # Dans la plateforme horizontalement
454 if pos_prev.y + obj.height <= rect.top:
455 # Position initale au-dessus de la plateforme
456 if pos_next.y + obj.height > rect.top:
457 # Nouvelle position dans ou sous la plateforme
458 obj.FLAG_JUMP = True
459 return (True, False,
460 Vec(pos_next.x, rect.top - obj.height))
462 elif pos_prev.y >= rect.bottom:
463 # Position initiale en-dessous de la plateforme
464 if pos_next.y < rect.bottom:
465 # Nouvelle position dans ou au-dessus de la plateforme
466 return (True, False, Vec(pos_next.x, rect.bottom))
468 elif pos_next.y + obj.height > rect.top\
469 and pos_next.y < rect.bottom:
470 # On ne considère que les collisions à gauche des plateformes
471 return (False, True, Vec(rect.left - obj.width, pos_next.y))
473 return(False, False, None)
476def update_pos_vel(obj, ground):
477 """
478 Met à jour la position et la vitesse de l'objet.
480 Parameters
481 ----------
482 obj : Player / Item
483 L'objet à mettre à jour
484 ground : Sprite group
485 Le groupe des plateformes
486 """
487 obj.vel += obj.acc
488 posnext = obj.pos + obj.vel + 0.5 * obj.acc
490 for plat in ground: # Gestion des collisions
491 coll = collide(obj, posnext, plat.rect)
492 if coll[0] or coll[1]:
493 posnext = coll[2]
494 if coll[0]:
495 obj.vel.y = 0
496 if coll[1]:
497 obj.vel.x = 0
499 obj.pos = posnext
500 obj.rect.topleft = obj.pos # Mise à jour de la position
503def onlydigits(value):
504 """
505 Filtre `value` pour ne garder que les chiffres.
507 On peut ainsi retirer toutes les sauts de lignes présents
508 dans le fichier `score.txt`.
510 Parameters
511 ----------
512 value : str
513 La chaîne à filtrer
515 Returns
516 -------
517 str
518 La chaîne obtenue après filtrage
519 """
520 final_chain = ""
521 for i in value:
522 if '0' <= i <= '9':
523 final_chain += i
524 return final_chain
527def onlyalphanum(value):
528 """
529 Filtre `value` pour ne garder que les caractères alphanumériques.
531 Parameters
532 ----------
533 value : str
534 La chaîne à filtrer
536 Returns
537 -------
538 str
539 La chaîne obtenue après filtrage
540 """
541 return re.sub(r'[^A-Za-z0-9]+', '', value)
544class GameObject(Sprite):
545 """
546 Classe des objets du monde (hors joueur).
548 Attributes
549 ----------
550 pos : int * int
551 Position de l'objet
552 scroll : float
553 Vitesse de déplacement
554 image : Sprite
555 Image de l'objet
556 rect : Rect
557 Rectangle encadrant l'objet
558 FLAG_creation : bool
559 Drapeau pour gérer la création des objets
560 """
562 def __init__(self, position, scroll, image):
563 """
564 Initialisation.
566 Parameters
567 ----------
568 position : int * int
569 Position de l'objet
570 scroll : float
571 Vitesse de déplacement
572 image : Sprite
573 image de l'objet
574 """
575 super().__init__()
576 self.pos = Vec(position)
577 self.scroll = scroll
578 # À 0 l'objet ne bouge pas,
579 # À 1 l'objet se déplace à la vitesse du sol
580 self.image = image
581 self.rect = self.image.get_rect(topleft=self.pos)
582 # Limite à partir de laquelle on génère un nouvel objet sur sa droite
583 # FLAG_creation est un flag pour ne générer qu'un seul nouvel objet
584 self.FLAG_creation = True
586 def update(self):
587 """Met à jour le vecteur position."""
588 posnext = self.pos + self.scroll * Vec(-cf.SPEED, 0)
589 self.pos = posnext
590 self.rect.topleft = self.pos
591 if self.rect.right < 0: # si l'objet sort de l'écran
592 # print(self)
593 self.kill() # on le supprime
594 # On met à jour l'image
595 cf.DISPLAYSURF.blit(self.image, self.rect)