def createNewCell(cls) -> None: if time.time() - cls.ref_time > cls.DELTA_T_NEW_CELL: cls.ref_time = time.time() x = random.randrange(Cell.BASE_RADIUS, cls.size.x - Cell.BASE_RADIUS) y = random.randrange(Cell.BASE_RADIUS, cls.size.y - Cell.BASE_RADIUS) cell = Cell(Vect2d(x, y)) ok = True for k in cls.creatures.keys(): enemy_list = cls.creatures[k] for enemy in enemy_list: if Vect2d.dist(enemy.pos, cell.pos) < cell.radius + enemy.radius: ok = False x = int(cell.pos.x / cls.size.x * cls.grid_size.x) y = int(cell.pos.y / cls.size.y * cls.grid_size.y) if ok: cls.all_cells.append(cell) cls.grid[x][y].append(cell) if len(cls.all_cells) >= cls.MAX_CELLS: cls.deleteCell(0)
def __init__(self, pos: Vect2d, name: str, color: Color, creature_id: int) -> None: """Constructeur Args: pos (Vect2d): position de la créature name (str): nom de la créature color (Color): couleur de la créature creature_id (int): id de la famille de la créature """ self.family = [self] self.invincibility_family_time = time.time() self.creature_id = creature_id self.killer_id = None self.pos = pos.copy() self.speed = Vect2d(0, 0) self.direction = Vect2d(0, 0) self.split_speed = 0 self.inertia = 0 self.radius = self.BASE_RADIUS self.color = color self.opposite_color = Color.oppositeColor(color) self.name = name self.img = Skins.getRandomSkin() self.score = Creature.BASE_SCORE self.is_alive = True
def drawCircle(cls, pos: Vect2d, color: Color, radius: int, base_pos: Vect2d = Vect2d(0, 0), fill: bool = True) -> None: """Procédure pour dessiner un cercle à l'écran Args: pos (Vect2d): position du centre du cercle color (Color): couleur RGBA radius (int): rayon du cercle base_pos (Vect2d): position de référence de la fenêtre fill (bool): cercle rempli ou non """ pos = pos.copy() if base_pos.length() != 0: pos = (pos - base_pos) * cls.zoom_factor pos = pos.toIntValues() radius = int(radius * cls.zoom_factor) if pos.x in range(-radius, cls.size.x + radius) and pos.y in range( -radius, cls.size.y + radius): if fill: pygame.gfxdraw.filled_circle(cls.window, pos.x, pos.y, radius, color) # Dessine un cercle plein pygame.gfxdraw.aacircle(cls.window, pos.x, pos.y, radius, color)
def init(cls) -> None: """Initialisation de la caméra""" Display.execWhenResized(cls.whenResized) cls.pos = Vect2d(0, 0) cls.window_center = Vect2d(0, 0)
def buttonWin_Display( button: Button, mouse_pos: Vect2d, ) -> None: """Affichage du texte de fin""" min_size = max(button.size.x, button.size.y) font_size = min_size * 50 / 400 Display.drawRect(Vect2d(0, 0), Display.size, (0, 0, 0, button.alpha)) if button.alpha < 127: button.alpha += 1 elif button.alpha < 255 and Map.game_finished: button.alpha += 1 score = Map.player_infos.get("score", 0) tps = Map.player_infos.get("time", 0) Display.drawText(button.text, button.pos + button.size / 2, color=button.color, size=font_size) Display.drawText("Score : " + str(score), button.pos + button.size / 2 + Vect2d(0, button.size.y), color=button.color, size=font_size) Display.drawText("Temps : " + str(tps) + " secondes", button.pos + button.size / 2 + Vect2d(0, button.size.y * 2), color=button.color, size=font_size)
def speedEnemies(self, map_size: Vect2d) -> tuple: dist_target = float("inf") dist_hunter = float("inf") speed_target = None speed_hunter = None can_split = False score_target = None for enemy_pos, enemy_radius, enemy_score in self.creatures_info: dist = Vect2d.dist(self.pos, enemy_pos) - self.radius - enemy_radius if Creature.canEat(enemy_score, self.score): if dist < dist_hunter: speed_hunter = enemy_pos - self.pos dist_hunter = dist elif Creature.canEat(self.score, enemy_score): if dist < dist_target: speed_target = enemy_pos - self.pos dist_target = dist score_target = enemy_score if score_target is not None: if score_target < self.score // 2: if dist_target > self.radius and dist_target < self.radius * 2: can_split = True if speed_hunter is None: speed_hunter = Vect2d(0, 0) if speed_target is None: speed_target = Vect2d(0, 0) coeff_target = 1 - dist_target / map_size.length() coeff_target = abs(coeff_target**3) if coeff_target == float("inf"): coeff_target = 0 elif coeff_target >= 1: coeff_target = 1 coeff_target = coeff_target if coeff_target != float("inf") else 0 coeff_hunter = 1 - dist_hunter / map_size.length() coeff_hunter = abs(coeff_hunter**3) if coeff_hunter == float("inf"): coeff_hunter = 0 elif coeff_hunter >= 1: coeff_hunter = 1 return speed_target, coeff_target, speed_hunter, coeff_hunter, can_split
def drawRect(cls, pos: Vect2d, size: Vect2d, color: Color, base_pos: Vect2d = Vect2d(0, 0), fill: bool = True) -> None: """Procédure pour dessiner un rectangle à l'écran Args: pos (Vect2d): position du coin en haut à gauche rectangle size (Vect2d): taille du rectangle color (Color): couleur RGBA base_pos (Vect2d): position de référence de la fenêtre fill (bool): rectangle rempli ou non """ size = size.copy() pos = pos.copy() if base_pos.length() != 0: pos = (pos - base_pos) * cls.zoom_factor pos = pos.toIntValues() if base_pos.length() != 0: size *= cls.zoom_factor size = size.toIntValues() rect = pygame.Rect(pos.toTuple(), size.toTuple()) if fill: pygame.gfxdraw.box(cls.window, rect, color) else: pygame.gfxdraw.rectangle(cls.window, rect, color)
def setMousePos(cls, mouse_pos: Vect2d) -> None: """Applique la nouvelle position de la souris Args: mouse_pos (Vect2d): nouvelle position """ for player in cls.creatures[cls.player_id]: player.mouse_pos = mouse_pos - Vect2d(Display.size.x / 2, Display.size.y / 2) player.mouse_pos += Camera.pos - player.pos + Vect2d( Display.size.x / 2, Display.size.y / 2)
def applyMenu(cls, width: int, height: int) -> None: """Procédure appelée pour afficher le menu Args: width (int): largeur de la fenêtre height (int): hauteur de la fenêtre """ cls.buttons.append(Button(pos=Vect2d(width/4, height/3), size=Vect2d(width/2, height/3), text="Jouer", on_click=cls.play, when_display=buttonStart_Display, when_init=buttonStart_Init))
def drawLine(cls, pos1: Vect2d, pos2: Vect2d, color: Color, base_pos: Vect2d = Vect2d(0, 0), width: int = 1) -> None: """Procédure pour dessiner une ligne à l'écran Args: pos1 (Vect2d): position de départ pos2 (Vect2d): position d'arrivée color (Color): couleur de la ligne base_pos (Vect2d): position de référence de la fenêtre width (int): largeur de la ligne """ pos1 = pos1.copy() pos2 = pos2.copy() if base_pos.length() != 0: pos1 = (pos1 - base_pos) * cls.zoom_factor pos2 = (pos2 - base_pos) * cls.zoom_factor pos1 = pos1.toIntValues() pos2 = pos2.toIntValues() if width == 1: pygame.gfxdraw.line(cls.window, pos1.x, pos1.y, pos2.x, pos2.y, color) else: pygame.draw.line(cls.window, color, pos1.toTuple(), pos2.toTuple(), width)
def init(cls, width: int, height: int, framerate: int) -> None: """Initialisation de l'affichage Args: width (int): largeur de la fenêtre height (int): hauteur de la fenêtre start_fullscreen (bool): plein écran au démarrage ou non framerate (int): nombre d'images par seconde """ cls.setCursorArrow() # On définit le curseur comme étant une flèche cls.screen_size = Vect2d(pygame.display.Info().current_w, pygame.display.Info().current_h) # Taille de l'écran cls.size = Vect2d(width, height) # Taille de la fenêtre os.environ['SDL_VIDEO_WINDOW_POS'] = "{0},{1}".format( *((cls.screen_size - cls.size) / 2).toTuple()) os.environ['SDL_VIDEO_CENTERED'] = '0' # Centre la fenêtre cls.windowed_size = cls.size.copy() # La taille fenêtre sera la même que la taille actuelle cls.framerate = framerate cls.real_framerate = cls.framerate ICON_PATH = "data/icon.png" try: f = open(ICON_PATH, 'r') except FileNotFoundError: print("Icône introuvable") else: f.close() icon = pygame.image.load(ICON_PATH) pygame.display.set_icon(icon) # On charge l'icône de la fenêtre cls.resize(cls.size.x, cls.size.y) # Cette opération va créer la fenêtre cls.updateFrame()
def reset(cls) -> None: """Réinitialise la map""" cls.start_time = time.time() cls.game_finished = False cls.player_infos = {} cls.all_cells = [] cls.ref_time = -1 * cls.DELTA_T_NEW_CELL cls.grid = [[[] for y in range(cls.grid_size.y)] for x in range(cls.grid_size.x)] cls.creatures = {} cls.player_id = cls.generateId() cls.focused_creature_id = cls.player_id player = Player(Vect2d(cls.size.x / 2, cls.size.y / 2), "Player", Color.randomColor(), cls.player_id) cls.creatures[cls.player_id] = [player] # Création du joueur cls.bushes = [] for i in range(config.NB_BUSHES): cls.createBush()
def updateFrame(cls, color: Color = Color.BLACK) -> None: """Procédure pour mettre à jour la fenêtre""" pygame.display.flip() # On met à jour l'écran if len(color) == 3: cls.window.fill(color) # On efface l'arrière-plan else: cls.drawRect(Vect2d(0, 0), cls.size, color) cls.updateTitle() del cls.frametimes[0] cls.frametimes.append(time.time() - cls.last_frametime) frametime = sum(cls.frametimes) / len(cls.frametimes) if frametime == 0: cls.real_framerate = float("inf") else: cls.real_framerate = round(1 / frametime) cls.last_frametime = time.time() # On met à jour les frametimes cls.clock.tick(cls.framerate) # Pour actualiser la fenêtre après un certain temps cls.framecount += 1
def detectCellHitbox(cls, creature: Creature) -> None: for i in range(len(cls.all_cells) - 1, -1, -1): cell_i = cls.all_cells[i] if Vect2d.distSq(creature.pos, cell_i.pos) < (creature.radius + cell_i.radius)**2: creature.score += cell_i.score cls.deleteCell(i)
def whenResized(cls, width: int, height: int) -> None: """Quand la fenêtre change de taille Args: width (int): nouvelle largeur de la fenêtre height (int): nouvelle hauteur de la fenêtre """ cls.window_center = Vect2d(width, height) / 2
def createEnemy(cls) -> None: """Crée un nouvel ennemi en évitant le spawn-kill, apparition sur une autre créature""" ok = False timed_out = False compt = 0 while not ok and not timed_out: ok = True pos = Vect2d( random.randrange(Creature.BASE_RADIUS * 2, cls.size.x - Creature.BASE_RADIUS * 2), random.randrange(Creature.BASE_RADIUS * 2, cls.size.y - Creature.BASE_RADIUS * 2)) for k in cls.creatures.keys(): creatures_list = cls.creatures[k] for creature in creatures_list: if Vect2d.dist(pos, creature.pos) < (Creature.BASE_RADIUS + creature.radius) * 2: ok = False if compt == 10: timed_out = True compt += 1 if not timed_out: enemy_id = cls.generateId() if cls.all_usernames is None: size = random.randint(3, 5) name = cls.generateNewName(size) else: name = cls.all_usernames[random.randrange( len(cls.all_usernames))] enemy = Enemy(pos, name, Color.randomColor(), enemy_id) cls.creatures[enemy_id] = [enemy]
def resize(cls, w: int, h: int) -> None: """Fonction pour changer la taille de la fenêtre Cette fonction va recréer la fenêtre Args: w (int): nouvelle largeur de la fenêtre h (int): nouvelle hauteur de la fenêtre """ if cls.is_fullscreen: cls.window = pygame.display.set_mode((w, h), pygame.FULLSCREEN) else: cls.window = pygame.display.set_mode((w, h), pygame.RESIZABLE) cls.windowed_size = Vect2d(w, h) cls.size = Vect2d(w, h) for func in cls.exec_when_resized: func(w, h)
def drawTriangle(cls, pos: Vect2d, color: Color, radius: int, angle: float, base_pos: Vect2d = Vect2d(0, 0), fill: bool = True) -> None: """Procédure pour dessiner un triangle à l'écran Args: pos (Vect2d): position du centre du rectangle color (Color): couleur RGBA radius (int): distance du centre aux sommets base_pos (Vect2d): position de référence de la fenêtre fill (bool): triangle rempli ou non """ pos = pos.copy() if base_pos.length() != 0: pos = (pos - base_pos) * cls.zoom_factor pos = pos.toIntValues() nodes = [] # Sommets du triangle for i in range(3): local_angle = 2 * i * math.pi / 3 + angle nodes.append( int(pos.x + math.cos(local_angle) * radius * cls.zoom_factor)) nodes.append( int(pos.y + math.sin(local_angle) * radius * cls.zoom_factor)) if fill: pygame.gfxdraw.filled_trigon(cls.window, *nodes, color) # Triangle plein pygame.gfxdraw.aatrigon(cls.window, *nodes, color)
def drawImg(cls, img: pygame.Surface, pos: Vect2d, base_pos: Vect2d = Vect2d(0, 0), radius: int = None) -> None: """Procédure pour dessiner une image circulaire à l'écran Args: img (pygame.Surface): image à dessiner pos (Vect2d): position de l'image base_pos (Vect2d): position de référence de la fenêtre radius (int): rayon désiré de l'image """ pos = pos.copy() if base_pos.length() != 0: pos = (pos - base_pos) * cls.zoom_factor pos = pos.toIntValues() radius = int(radius * cls.zoom_factor) if radius is not None: pos -= Vect2d(radius, radius) img = pygame.transform.smoothscale(img, (radius * 2, radius * 2)) # On agrandit (ou réduit) la taille de l'image cls.window.blit(img, pos.toTuple())
def applyWin(cls, width: int, height: int) -> None: """Procédure appelée pour afficher la victoire du jeu Args: width (int): largeur de la fenêtre height (int): hauteur de la fenêtre """ cls.buttons.append(Button(pos=Vect2d(width/4, height/5), size=Vect2d(width/2, height/10), text="Gagné !", when_display=buttonWin_Display, when_init=lambda b: buttonWinOrEnd_Init(b, cls.first_try, Color.GREEN))) cls.buttons.append(Button(pos=Vect2d(width/5, 3*height/5), size=Vect2d(width/5, height/5), text="Rejouer", on_click=cls.play, when_display=buttonEndChoice_Display, when_init=lambda b: buttonWinOrEnd_Init(b, cls.first_try, Color.GREEN))) cls.buttons.append(Button(pos=Vect2d(3*width/5, 3*height/5), size=Vect2d(width/5, height/5), text="Quitter", on_click=cls.quit, when_display=buttonEndChoice_Display, when_init=lambda b: buttonWinOrEnd_Init(b, cls.first_try, Color.GREEN)))
def init(cls, width: int, height: int) -> None: """Initialisation de la map Args: width (int): largeur de la map height (int): hauteur de la map """ Creature.notifyMapNewCreature = cls.createCreatureFromParent cls.size = Vect2d(width, height) cls.MAX_CELLS = config.MAX_CELLS cls.NB_CELL_PER_SECOND = config.NB_CELL_PER_SECOND cls.DELTA_T_NEW_CELL = config.DELTA_T_NEW_CELL cls.MAX_SPLIT = config.MAX_SPLIT cls.grid_size = Vect2d(config.GRID_WIDTH, config.GRID_HEIGHT) cls.CREATURES_MAX_SIZE = config.NB_ENEMIES + 1 try: f = open("./data/usernames.txt", 'r') except FileNotFoundError: print("Pas de fichier usernames.txt") cls.all_usernames = None else: cls.all_usernames = [] line = f.readline() while line != "": line = line.replace('\n', '') line = line.replace('\r', '') cls.all_usernames.append(line) line = f.readline() f.close() cls.reset()
def createBush(cls) -> None: """Crée un buisson qui ne soit pas en collision avec un autre buisson""" ok = False timeout = 0 while not ok and timeout < 1000: ok = True pos = Vect2d( random.randrange(Bush.RADIUS * 2, cls.size.x - Bush.RADIUS * 2), random.randrange(Bush.RADIUS * 2, cls.size.x - Bush.RADIUS * 2)) for bush in cls.bushes: if Vect2d.dist(pos, bush.pos) < Bush.RADIUS * 4: ok = False timeout += 1 if ok: cls.bushes.append(Bush(pos))
def handleBushSplit(cls) -> None: """Gère le split lié aux buissons""" for k in cls.creatures.keys(): creatures_list = cls.creatures[k] for creature in creatures_list: if creature.radius > Bush.RADIUS: for bush in cls.bushes: if Vect2d.dist(bush.pos, creature.pos) < creature.radius: bush.hit() creature.split(is_player=( creature.creature_id == cls.player_id), override_limit=True)
def getMapPos(self, size: Vect2d, grid_size: Vect2d) -> Vect2d: """Permet de translater une position absolue en position dans une grille Args: size (Vect2d): taille de l'espace d'origine grid_size (Vect2d): taille de l'espace d'arrivée Returns: Vect2d: les 2 positions """ pos_x = int(self.pos.x/size.x * grid_size.x) pos_y = int(self.pos.y/size.y * grid_size.y) return Vect2d(pos_x, pos_y)
def searchCellDest(self, radius: int, map_size: Vect2d) -> None: maxi = 0 liste_pos_maxi = [] score = 0 grid_size = Vect2d(len(self.map_cell), len(self.map_cell[0])) map_pos = self.getMapPos(map_size, grid_size) for x in range(map_pos.x - radius, map_pos.x + radius + 1): for y in range(map_pos.y - radius, map_pos.y + radius + 1): if x in range(grid_size.x) and y in range(grid_size.y): taille = len(self.map_cell[x][y]) if taille == maxi: liste_pos_maxi += [(x, y)] elif taille > maxi: maxi = taille liste_pos_maxi = [(x, y)] distance_mini = float("inf") coords_mini = None for x, y in liste_pos_maxi: for j in range(len(self.map_cell[x][y])): pos = self.map_cell[x][y][j] dist = Vect2d.dist(pos, self.pos) if dist < distance_mini: score = len(self.map_cell[x][y]) distance_mini = dist coords_mini = self.map_cell[x][y][j] return coords_mini, score
def getFocusedPos(cls) -> Vect2d: """Retourne la position de focus, pour la caméra Returns: pos (Vect2d): position """ pos = Vect2d(0, 0) for creature in cls.creatures[cls.focused_creature_id]: pos += creature.pos pos /= len(cls.creatures[cls.focused_creature_id]) return pos
def __init__(self, pos: Vect2d, name: str, color: 'Color', creature_id: int) -> None: """Constructeur Args: pos (Vect2d): position du centre de l'ennemi name (str): nom de l'ennemi color (Color): couleur de l'ennemi creature_id (int): id de la famille de la créature """ super().__init__(pos, name, color, creature_id) self.map_cell = None self.creatures_info = None self.speed = Vect2d(random.random() * 2 - 1, random.random() * 2 - 1)
def detectEnemyHitbox(cls) -> None: """Gère la hitbox des créatures""" for k1 in cls.creatures.keys(): for k2 in cls.creatures.keys(): enemy_list_1 = cls.creatures[k1] enemy_list_2 = cls.creatures[k2] for enemy_1 in enemy_list_1: for enemy_2 in enemy_list_2: if enemy_1 is not enemy_2 and enemy_1.is_alive and enemy_2.is_alive: dist = Vect2d.dist(enemy_1.pos, enemy_2.pos) if k1 == k2: t1 = time.time( ) - enemy_1.invincibility_family_time t2 = time.time( ) - enemy_2.invincibility_family_time if t1 > Creature.SPLIT_TIME and t2 > Creature.SPLIT_TIME: if dist <= max(enemy_1.radius, enemy_2.radius): enemy_1.kill(enemy_2.score) enemy_2.killed(k1) family_tmp = [] for creature in enemy_1.family: if creature is not enemy_2: family_tmp.append(creature) enemy_1.family = family_tmp else: if Creature.canEat(enemy_1.radius, enemy_2.radius): if dist <= max(enemy_1.radius, enemy_2.radius): enemy_1.kill(enemy_2.score) enemy_2.killed(k1)
def buttonEnd_Display(button: Button, mouse_pos: Vect2d) -> None: """Affichage du texte de fin""" min_size = max(button.size.x, button.size.y) font_size = min_size * 50 / 400 # Taille de la police Display.drawRect(Vect2d(0, 0), Display.size, (0, 0, 0, button.alpha)) # Fondu transparent if button.alpha < 127: # Si le jeu est en cours on a un fondu jusqu'à 50% button.alpha += 1 elif button.alpha < 255 and Map.game_finished: # Sinon le fondu se fait jusqu'à 100% button.alpha += 1 Display.drawText(button.text, button.pos + button.size / 2, color=button.color, size=font_size)
class Player(Creature): """Créature joueur Attributs: mouse_pos (Vect2d): position de la souris """ mouse_pos = Vect2d() def __init__(self, pos: Vect2d, name: str, color: 'Color', creature_id: int) -> None: """Constructeur Args: pos (Vect2d): position du joueur name (str): nom du joueur color (Color): couleur du joueur creature_id (int): id de la famille du joueur """ super().__init__(pos, name, color, creature_id) def update(self, map_size: Vect2d) -> None: """Met à jour le joueur Args: map_size (Vect2d): taille de la map """ if self.mouse_pos.lengthSq() > self.BASE_RADIUS**2: coeff_dist_mouse = 1 else: coeff_dist_mouse = self.mouse_pos.length()/self.BASE_RADIUS coeff_dist_mouse = coeff_dist_mouse**2 self.speed = self.mouse_pos.normalize()*coeff_dist_mouse + self.speed*0.95 self.direction = self.speed.normalize()*coeff_dist_mouse self.applySpeed(map_size)