275 lines
13 KiB
Python
275 lines
13 KiB
Python
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
|