Agar.io by ChatGPT
import pygame import random import math import sys import array pygame.init() pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512) # --------------------- USTAWIENIA GRY --------------------- # Rozmiar okna WIN_WIDTH, WIN_HEIGHT = 800, 600 # Rozmiar „świata” WORLD_WIDTH, WORLD_HEIGHT = 1600, 1200 FPS = 60 # Kolory WHITE = (255, 255, 255) BLACK = ( 0, 0, 0) BLUE = ( 0, 0, 255) # Przygotowanie okna screen = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) pygame.display.set_caption("Agar.io z mądrzejszymi NPC i rankingiem") clock = pygame.time.Clock() # --------------------- GENEROWANIE DŹWIĘKÓW --------------------- def generate_sine_wave(frequency=440, duration_ms=200, volume=0.5, sample_rate=44100): """ Generuje tablicę bajtów zawierającą próbki sinusoidy (16 bit, stereo) o zadanej częstotliwości, czasie trwania i głośności. """ num_samples = int(sample_rate * duration_ms / 1000.0) amplitude = int(32767 * volume) # dla 16-bit max = 32767 samples = array.array('h') # 'h' = short (16-bit) - kanał lewy for i in range(num_samples): sample_value = int(amplitude * math.sin(2.0 * math.pi * frequency * i / sample_rate)) samples.append(sample_value) # Tworzymy stereo: kopiujemy próbkę L jako R stereo_samples = array.array('h') for s in samples: stereo_samples.append(s) # left stereo_samples.append(s) # right sound = pygame.mixer.Sound(buffer=stereo_samples.tobytes()) return sound sound_eat_food = generate_sine_wave(frequency=600, duration_ms=100, volume=0.5) sound_eat_npc = generate_sine_wave(frequency=300, duration_ms=200, volume=0.6) # --------------------- KLASY OBIEKTÓW --------------------- class Blob: """Podstawowa klasa dla wszelkich 'kulek' (gracz, NPC, jedzenie).""" def __init__(self, x, y, radius, color=BLACK, speed=0): self.x = x self.y = y self.radius = radius self.color = color self.speed = speed def distance_to(self, other) -> float: return math.hypot(self.x - other.x, self.y - other.y) def check_collision(self, other) -> bool: return self.distance_to(other) <= self.radius + other.radius def draw(self, surface, camera_offset_x, camera_offset_y): screen_x = int(self.x - camera_offset_x) screen_y = int(self.y - camera_offset_y) pygame.draw.circle(surface, self.color, (screen_x, screen_y), self.radius) class Player(Blob): """Klasa dla gracza (sterowanego przez człowieka).""" def __init__(self, x, y, radius, color, speed, name="TY"): super().__init__(x, y, radius, color, speed) self.name = name def move(self): keys = pygame.key.get_pressed() dx, dy = 0, 0 if keys[pygame.K_w] or keys[pygame.K_UP]: dy = -1 if keys[pygame.K_s] or keys[pygame.K_DOWN]: dy = 1 if keys[pygame.K_a] or keys[pygame.K_LEFT]: dx = -1 if keys[pygame.K_d] or keys[pygame.K_RIGHT]: dx = 1 # Normalizacja wektora ruchu if dx != 0 or dy != 0: length = math.hypot(dx, dy) dx /= length dy /= length # Ruch self.x += dx * self.speed self.y += dy * self.speed # Ograniczenie do obszaru świata if self.x - self.radius < 0: self.x = self.radius if self.x + self.radius > WORLD_WIDTH: self.x = WORLD_WIDTH - self.radius if self.y - self.radius < 0: self.y = self.radius if self.y + self.radius > WORLD_HEIGHT: self.y = WORLD_HEIGHT - self.radius class NPC(Blob): """Klasa dla 'mądrzejszych' NPC.""" def __init__(self, x, y, radius, color, speed, name): super().__init__(x, y, radius, color, speed) self.name = name # Kierunek startowy - losowy self.dir_x = random.choice([-1, 1]) self.dir_y = random.choice([-1, 1]) def move(self, player, npcs): """ Prostsza 'inteligencja': - Szuka najbliższego obiektu (player lub inny NPC). - Jeśli tamten jest większy -> uciekamy. - Jeśli tamten jest mniejszy -> gonimy. - Jeśli w pobliżu nie ma nikogo -> losowy ruch (z niewielką szansą na zmianę kierunku). """ # Najpierw szukamy najbliższego 'gracza' lub NPC (z pominięciem samego siebie). target = self.find_closest(player, npcs) if target is not None: # Obliczamy odległość i wektor do celu dist = self.distance_to(target) dx = (target.x - self.x) dy = (target.y - self.y) if dist != 0: dx /= dist dy /= dist # Decyzja w zależności od wielkości if target.radius > self.radius: # Uciekamy -> ruch w przeciwną stronę dx = -dx dy = -dy else: # Gonimy -> dx, dy jak powyżej (czyli w stronę celu) pass # Poruszamy się z własną prędkością w wyznaczonym kierunku self.x += dx * self.speed self.y += dy * self.speed else: # Brak celu w zasięgu -> losowy ruch, ze zmianą kierunku 1% if random.random() < 0.01: self.dir_x = random.choice([-1, 1]) self.dir_y = random.choice([-1, 1]) self.x += self.dir_x * self.speed self.y += self.dir_y * self.speed # Odbijanie od krawędzi świata if self.x - self.radius < 0: self.x = self.radius self.dir_x *= -1 if self.x + self.radius > WORLD_WIDTH: self.x = WORLD_WIDTH - self.radius self.dir_x *= -1 if self.y - self.radius < 0: self.y = self.radius self.dir_y *= -1 if self.y + self.radius > WORLD_HEIGHT: self.y = WORLD_HEIGHT - self.radius self.dir_y *= -1 def find_closest(self, player, npcs, vision_radius=300): """ Znajdź najbliższy obiekt (NPC lub gracza) w promieniu 'vision_radius'. Zwraca obiekt (Player/NPC) lub None, jeśli nic nie znaleziono. """ closest_obj = None min_dist = float('inf') # Najpierw sprawdzamy gracza if player != self: dist = self.distance_to(player) if dist < min_dist and dist <= vision_radius: min_dist = dist closest_obj = player # Następnie sprawdzamy innych NPC for npc in npcs: if npc is self: continue # omijamy samego siebie dist = self.distance_to(npc) if dist < min_dist and dist <= vision_radius: min_dist = dist closest_obj = npc return closest_obj # --------------------- FUNKCJE POMOCNICZE --------------------- def create_npcs(n): """ Tworzy n NPC-ów w losowych miejscach na planszy. Używamy podanej listy nazw i wybieramy je losowo. """ possible_names = [ "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Gamma", "Hotel", "India", "Juliet", "Kombo", "Mike", "November", "Oscar", "Papa", "Quebec", "Romeo", "Tango", "Uniform", "Victor", "Xray", "Zero" ] npcs = [] for _ in range(n): radius = random.randint(10, 20) x = random.randint(radius, WORLD_WIDTH - radius) y = random.randint(radius, WORLD_HEIGHT - radius) color = ( random.randint(50, 255), random.randint(50, 255), random.randint(50, 255) ) speed = random.randint(1, 3) name = random.choice(possible_names) npcs.append(NPC(x, y, radius, color, speed, name)) return npcs def create_food(n): """Tworzy n elementów 'jedzenia' (małe kulki).""" food_list = [] for _ in range(n): radius = random.randint(3, 5) x = random.randint(radius, WORLD_WIDTH - radius) y = random.randint(radius, WORLD_HEIGHT - radius) color = ( random.randint(100, 255), random.randint(100, 255), random.randint(100, 255) ) food_list.append(Blob(x, y, radius, color, 0)) return food_list def show_end_screen(message): """Wyświetla ekran końcowy z komunikatem.""" font = pygame.font.SysFont(None, 80) text_surface = font.render(message, True, (255, 0, 0)) rect = text_surface.get_rect(center=(WIN_WIDTH // 2, WIN_HEIGHT // 2)) screen.fill(WHITE) screen.blit(text_surface, rect) pygame.display.flip() pygame.time.wait(3000) def draw_scoreboard(surface, player, npcs): """ Rysuje w lewym górnym rogu listę aktywnych „graczy” (gracz + NPC) posortowaną malejąco po promieniu (radius). Format: NICK (radius) """ font = pygame.font.SysFont(None, 26) all_players = [player] + npcs all_players.sort(key=lambda p: p.radius, reverse=True) x = 10 y = 10 for blob in all_players: text_surface = font.render(f"{blob.name} ({blob.radius})", True, BLACK) surface.blit(text_surface, (x, y)) y += 25 # odstęp między kolejnymi liniami # --------------------- LOGIKA GŁÓWNA --------------------- def main(): # Tworzymy obiekty player = Player(WORLD_WIDTH//2, WORLD_HEIGHT//2, 15, BLUE, speed=3, name="TY") npcs = create_npcs(10) food = create_food(50) # Można zwiększyć liczbę jedzenia running = True while running: clock.tick(FPS) # Obsługa zdarzeń for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Ruch gracza i NPC player.move() for npc in npcs: npc.move(player, npcs) # mądrzejszy ruch # Kolizje z jedzeniem – dźwięk TYLKO gdy gracz zjada for f in food[:]: if player.check_collision(f): sound_eat_food.play() # dźwięk tylko dla gracza player.radius += f.radius // 2 food.remove(f) else: # Sprawdzamy, czy któryś z NPC zjadł jedzenie (bez dźwięku) for npc in npcs: if npc.check_collision(f): npc.radius += f.radius // 2 food.remove(f) break # Kolizje między graczem a NPC for npc in npcs[:]: if player.check_collision(npc): if player.radius > npc.radius: # Gracz zjada NPC -> dźwięk sound_eat_npc.play() player.radius += npc.radius // 2 npcs.remove(npc) else: # Gracz zostaje zjedzony show_end_screen("Przegrana!") running = False break # Kolizje między NPC-ami (bez dźwięku) for i in range(len(npcs)): if npcs[i] is None: continue for j in range(i+1, len(npcs)): if npcs[j] is None: continue if npcs[i].check_collision(npcs[j]): if npcs[i].radius > npcs[j].radius: npcs[i].radius += npcs[j].radius // 2 npcs[j] = None else: npcs[j].radius += npcs[i].radius // 2 npcs[i] = None break npcs = [npc for npc in npcs if npc is not None] # Sprawdzenie zwycięstwa: WYGRANA, jeśli nie ma żadnych NPC if len(npcs) == 0: show_end_screen("Wygrana!") running = False # Rysowanie tła screen.fill(WHITE) # Kamera (offset) camera_offset_x = player.x - WIN_WIDTH // 2 camera_offset_y = player.y - WIN_HEIGHT // 2 if camera_offset_x < 0: camera_offset_x = 0 if camera_offset_x > WORLD_WIDTH - WIN_WIDTH: camera_offset_x = WORLD_WIDTH - WIN_WIDTH if camera_offset_y < 0: camera_offset_y = 0 if camera_offset_y > WORLD_HEIGHT - WIN_HEIGHT: camera_offset_y = WORLD_HEIGHT - WIN_HEIGHT # Rysowanie jedzenia, NPC i gracza for f in food: f.draw(screen, camera_offset_x, camera_offset_y) for npc in npcs: npc.draw(screen, camera_offset_x, camera_offset_y) player.draw(screen, camera_offset_x, camera_offset_y) # Rysowanie rankingu draw_scoreboard(screen, player, npcs) pygame.display.flip() pygame.quit() sys.exit() if __name__ == "__main__": main()
Leave a Comment