import pygame import time import sys import random """ Game of Life Simulator """ #Partie Calculatoire RULES = {"B":3, "O":3, "U":2} NEIGHBOURS = [(i,j) for i in (-1,0,1) for j in (-1,0,1) if (i,j) != (0,0)] def relative(offset, cell): """ Fonction permettant d'obtenir une coordonnée absolue à partir d'une origine(offset) et de coordonnées relative(cell). """ return tuple(cell[i] + offset[i] for i in range(len(cell))) def difference(origin, cell): """ Fonction permettant d'obtenir les coordonnées de cell en fixant origin comme origine """ return tuple(cell[i] - origin[i] for i in range(len(cell))) def scale(cell, factor): return tuple(int(cell[i]*factor) for i in range(len(cell))) class Grid: """ Classe gérant les calculs liés à l'évolution d'une grille infinie selon le Jeu de la vie """ def __init__(self): self.alive_cells = set({(i, 0) for i in range(500)}) self.active_cells = dict() self.old_cells = set() def tick(self): self.old_cells.clear() self.old_cells.update(self.alive_cells) self.active_cells.clear() #On s'assure que les cellules vivantes soient considérés comme actives for cell in self.alive_cells: self.active_cells.update({cell:0}) #On met à jour les voisins des cellules vivantes, c'est à dire incrémenter leur compteur for cell in self.alive_cells: for neighbour in NEIGHBOURS: self.incr_counter(relative(cell, neighbour)) #On applique les règles du jeu selon le compteur des cellules actives for cell in self.active_cells: if RULES["U"] <= self.active_cells[cell] <= RULES["O"]: if self.active_cells[cell] == RULES["B"]: self.alive_cells.add(cell) else: self.alive_cells.discard(cell) def incr_counter(self, cell): if cell in self.active_cells: self.active_cells[cell] +=1 else: self.active_cells.update({cell:1}) #Interface Graphique pygame.init() size = width, height = 1025,1025 screen = pygame.display.set_mode(size, flags=pygame.RESIZABLE) DEBUG = False #Affichage de variables de débogage à l'écran TICK_RATE = 10 #Fréquence de rafraîchissement du jeu FRAME_RATE = 60 #Fréquence de rafraîchissement de l'interface camera = [0, 0] #Permettant choisir quelle zone de la grille on visualise true_camera = camera MOUSE = True #Mode souris : On pointe avec les cellules avec la souris et pas les flèches directionnelles grid = Grid() #On initialise une grille #On charge les assets source_assets = { "alive_cell": pygame.image.load("assets/alive_cell.png"), "dead_cell": pygame.image.load("assets/dead_cell.png"), "highlight": pygame.image.load("assets/highlight.png")} SCALE = .25 #Facteur de taille variable (zoom) CELL_SIDE = int(source_assets["highlight"].get_rect().width * SCALE) #Mesure en pixel d'un côté d'une cellule assets = {asset:pygame.transform.scale(source_assets[asset],(CELL_SIDE,CELL_SIDE)) for asset in source_assets} font = pygame.font.SysFont(pygame.font.get_default_font(), CELL_SIDE) class Unit(pygame.sprite.Sprite): """ Classe représentant une unité d'affichage (cellule) Il est à noter que cette unité peut représenter différentes cellules en fonction de camera """ def __init__(self, offset): super().__init__() self.offset = offset def draw(self, screen, camera, grid): self.image = assets[("dead_cell","alive_cell")[relative(camera, self.offset) in grid.alive_cells]] self.position = self.image.get_rect() self.position.x = self.offset[0]*CELL_SIDE #On positionne la cellule sur l'écran self.position.y = self.offset[1]*CELL_SIDE #On positionne la cellule sur l'écran screen.blit(self.image, self.position) #On dessine la cellule if DEBUG and relative(camera, self.offset) in grid.active_cells: text = font.render(str(grid.active_cells[relative(camera, self.offset)]), False, (0,255,0)) posi = text.get_rect() posi.center = self.position.center screen.blit(text, posi) class Pointer(pygame.sprite.Sprite): """ Classe représentant le pointeur de cellule sélectionnée (pour l'éditer) """ def __init__(self, pos): super().__init__() self.pos = pos def draw(self, screen, camera, grid): self.image = assets["highlight"] self.position = self.image.get_rect() self.position.x = self.pos[0]*CELL_SIDE #On positionne le pointeur sur l'écran self.position.y = self.pos[1]*CELL_SIDE #On positionne le pointeur sur l'écran screen.blit(self.image, self.position) #On dessine le pointeur def draw_grid(screen, camera, grid, units): for unit in units: units[unit].draw(screen, camera, grid) #Boucle principale def get_units(): return {(i,j):Unit((i,j)) for i in range(width//(CELL_SIDE) +1) for j in range(height//(CELL_SIDE) +1)} units = get_units() #Liste de cellules à afficher à l'écran relatives = {unit:relative(camera, unit) for unit in units} pointer = Pointer((0,0)) #Permettant de sélectionner une cellule précisément pour l'éditer running = True pause = True last_tick = time.time() last_frame = time.time() KEYSDOWN = set() MOUSEBUTTONSDOWN = set() visited_cells = set() drag_origin = (0,0) #Position du curseur à la préssion du bouton du milieu de la souris last_pointer_move = time.time() redraw = True while running: frame = (last_frame + 1/FRAME_RATE) - time.time() < 0 if frame: for event in pygame.event.get(): if event.type == pygame.VIDEORESIZE: size = width, height = event.size screen =screen = pygame.display.set_mode(size, flags=pygame.RESIZABLE) units = get_units() redraw = True if event.type == pygame.QUIT: sys.exit() if event.type == pygame.KEYDOWN: KEYSDOWN.add(event.key) if event.key in [275,276,273,274] and not MOUSE: #Touches directionnelles pour déplacer le pointeur new_pos = list(pointer.pos) exec("new_pos" + ("[0]+","[0]-","[1]-","[1]+")[[275,276,273,274].index(event.key)] + "=1") if pointer.pos in units: units[pointer.pos].draw(screen, camera, grid) pointer.pos = tuple(new_pos) last_pointer_move = time.time() if event.key == 116 and not MOUSE: #T: Changer l'état d'une cellule if relative(camera, pointer.pos) in grid.alive_cells: grid.alive_cells.discard(relative(camera, pointer.pos)) else: grid.alive_cells.add(relative(camera, pointer.pos)) visited_cells.add(relative(camera, pointer.pos)) if event.type == pygame.KEYUP: print(event.key) if event.key in KEYSDOWN: if event.key == 32: #ESPACE : pause pause = not pause if event.key in (109,59): #M : Mode souris MOUSE = not MOUSE if event.key == 100: #D : Mode de déboggage DEBUG = not DEBUG if event.key in (113,97): #Q : Quitter sys.exit() if event.key == 127: #Suppr : Effacer la grille grid.alive_cells.clear() redraw = True if event.key == 99: #C: Diminuer le TICK_RATE TICK_RATE = TICK_RATE/2 if event.key == 118: #V : Augmenter le TICK_RATE TICK_RATE = TICK_RATE*2 if event.key == 116 and not MOUSE: #T: Changer l'état d'une cellule visited_cells.clear() if event.key == 114 : #R : Remplir aléatoirement l'écran redraw = True for unit in units: if random.randint(0,3) == 0: grid.alive_cells.add(relatives[unit]) else: grid.alive_cells.discard(relatives[unit]) KEYSDOWN.discard(event.key) if event.type == pygame.MOUSEBUTTONDOWN: if MOUSE: if event.button in (2,3): #Bouton de la molette / Clic droit : Glisser print(event.pos) true_camera = camera drag_origin = event.pos if event.button == 1: #Clic gauche : Éditer la cellule selectionnée if relative(camera, pointer.pos) in grid.alive_cells: grid.alive_cells.discard(relative(camera, pointer.pos)) else: grid.alive_cells.add(relative(camera, pointer.pos)) visited_cells.add(relative(camera, pointer.pos)) MOUSEBUTTONSDOWN.add(event.button) if event.type == pygame.MOUSEBUTTONUP: if event.button in (2,3): camera = difference(scale(difference(drag_origin, event.pos), 1/CELL_SIDE), true_camera) redraw = True if event.button == 1: visited_cells.clear() if event.button == 4: #Molette vers le haut #Zoom if CELL_SIDE*2 <= min(size): SCALE *= 2 #Facteur de taille variable (zoom) CELL_SIDE = int(source_assets["highlight"].get_rect().width * SCALE) #Mesure en pixel d'un côté d'une cellule assets = {asset:pygame.transform.scale(source_assets[asset],(CELL_SIDE,CELL_SIDE)) for asset in source_assets} units = get_units() #Liste de cellules à afficher à l'écran font = pygame.font.SysFont(pygame.font.get_default_font(), CELL_SIDE) redraw = True if event.button == 5: #Molette vers le bas #"Dézoom" if CELL_SIDE/2 >= 3: SCALE *= 1/2 #Facteur de taille variable (zoom) CELL_SIDE = int(source_assets["highlight"].get_rect().width * SCALE) #Mesure en pixel d'un côté d'une cellule assets = {asset:pygame.transform.scale(source_assets[asset],(CELL_SIDE,CELL_SIDE)) for asset in source_assets} units = get_units() #Liste de cellules à afficher à l'écran font = pygame.font.SysFont(pygame.font.get_default_font(), CELL_SIDE) redraw = True print(event.button) MOUSEBUTTONSDOWN.discard(event.button) if event.type == pygame.MOUSEMOTION: if 2 in MOUSEBUTTONSDOWN or 3 in MOUSEBUTTONSDOWN: camera = difference(scale(difference(drag_origin, event.pos), 1/CELL_SIDE),true_camera) redraw = True if MOUSE: if pointer.pos in units: units[pointer.pos].draw(screen, camera, grid) pointer.pos = scale(event.pos, 1/CELL_SIDE) if 1 in MOUSEBUTTONSDOWN: if pointer.pos in units: units[pointer.pos].draw(screen, camera, grid) pointer.pos = scale(event.pos, 1/CELL_SIDE) if relative(camera, pointer.pos) not in visited_cells: if relative(camera, pointer.pos) in grid.alive_cells: grid.alive_cells.discard(relative(camera, pointer.pos)) else: grid.alive_cells.add(relative(camera, pointer.pos)) visited_cells.add(relative(camera, pointer.pos)) if not MOUSE and (time.time() - (last_pointer_move + 5/FRAME_RATE) > 0): for key in KEYSDOWN: if key in [275,276,273,274]: new_pos = list(pointer.pos) exec("new_pos" + ("[0]+","[0]-","[1]-","[1]+")[[275,276,273,274].index(event.key)] + "=1") if pointer.pos in units: units[pointer.pos].draw(screen, camera, grid) pointer.pos = tuple(new_pos) last_pointer_move = time.time() if redraw: relatives = {unit:relative(camera, unit) for unit in units} draw_grid(screen, camera, grid, units) redraw = False if (last_tick + 1/TICK_RATE) - time.time() < 0: last_tick = time.time() if not pause: grid.tick() if True: for unit in units: if (relatives[unit] in grid.alive_cells) != (relatives[unit] in grid.old_cells): units[unit].draw(screen, camera, grid) if frame: if pointer.pos in units: units[pointer.pos].draw(screen, camera, grid) pointer.draw(screen, camera, grid) pygame.display.flip() last_frame = time.time() time.sleep(max(0,min((last_frame + 1/FRAME_RATE) - time.time(), (last_tick + 1/TICK_RATE) - time.time()))) #On attend avant la prochaine image / le prochain tick pour ne pas surcharger le CPU inutilement