import pygame import random import json import os from datetime import datetime # Initialisation de Pygame pygame.init() # Constantes WIDTH, HEIGHT = 800, 900 FPS = 60 # Couleurs BLACK = (0, 0, 0) WHITE = (255, 255, 255) CYAN = (0, 255, 255) YELLOW = (255, 255, 0) RED = (255, 0, 0) MAGENTA = (255, 0, 255) GREEN = (0, 255, 0) PINK = (255, 105, 180) ORANGE = (255, 102, 0) BLUE = (0, 170, 255) # Création de la fenêtre screen = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("🚀 Space Shooter") clock = pygame.time.Clock() # Police font_small = pygame.font.Font(None, 24) font_medium = pygame.font.Font(None, 36) font_large = pygame.font.Font(None, 72) class Star: def __init__(self): self.x = random.randint(0, WIDTH) self.y = random.randint(0, HEIGHT) self.size = random.randint(1, 3) self.speed = random.uniform(0.5, 2.5) def update(self): self.y += self.speed if self.y > HEIGHT: self.y = 0 self.x = random.randint(0, WIDTH) def draw(self, surface): alpha = random.randint(100, 255) color = (alpha, alpha, alpha) pygame.draw.rect(surface, color, (self.x, self.y, self.size, self.size)) class Player: def __init__(self): self.x = WIDTH // 2 - 25 self.y = HEIGHT - 100 self.w = 50 self.h = 50 self.speed = 7 self.flame_offset = 0 self.engine_glow = 0 def update(self, keys): if keys[pygame.K_LEFT] and self.x > 0: self.x -= self.speed if keys[pygame.K_RIGHT] and self.x < WIDTH - self.w: self.x += self.speed if keys[pygame.K_UP] and self.y > 0: self.y -= self.speed if keys[pygame.K_DOWN] and self.y < HEIGHT - self.h: self.y += self.speed self.flame_offset = (self.flame_offset + 1) % 10 self.engine_glow = (self.engine_glow + 0.2) % 6.28 def draw(self, surface): # Ombre portée shadow = pygame.Surface((self.w + 20, self.h + 20), pygame.SRCALPHA) pygame.draw.ellipse(shadow, (0, 0, 0, 80), (0, 0, self.w + 20, self.h + 20)) surface.blit(shadow, (self.x - 10, self.y + 15)) # Lueur extérieure pulsante import math glow_intensity = int(30 + 20 * abs(math.sin(self.engine_glow))) for radius in range(40, 25, -5): alpha = glow_intensity - (40 - radius) * 2 if alpha > 0: glow = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA) pygame.draw.circle(glow, (0, 200, 255, alpha), (radius, radius), radius) surface.blit(glow, (self.x + self.w // 2 - radius, self.y + self.h // 2 - radius)) # Corps principal du vaisseau (gris métallique avec reflets) # Base pygame.draw.ellipse(surface, (120, 120, 130), (self.x + 8, self.y + 22, 34, 23)) # Reflets métalliques pygame.draw.ellipse(surface, (180, 180, 200), (self.x + 10, self.y + 20, 30, 20)) pygame.draw.ellipse(surface, (160, 160, 180), (self.x + 12, self.y + 24, 26, 16)) # Aileron central wing_points = [(self.x + 25, self.y), (self.x + 20, self.y + 15), (self.x + 30, self.y + 15)] pygame.draw.polygon(surface, (140, 140, 160), wing_points) pygame.draw.polygon(surface, (180, 180, 200), wing_points, 2) # Cockpit (plusieurs couches pour effet de profondeur) pygame.draw.ellipse(surface, (0, 150, 220), (self.x + 14, self.y + 14, 22, 22)) pygame.draw.ellipse(surface, (0, 200, 255), (self.x + 16, self.y + 16, 18, 18)) pygame.draw.ellipse(surface, (100, 230, 255), (self.x + 18, self.y + 18, 14, 14)) # Reflet du cockpit pygame.draw.ellipse(surface, (200, 240, 255), (self.x + 20, self.y + 19, 8, 6)) # Ailes latérales avec dégradés # Aile gauche wing_left = [(self.x, self.y + 28), (self.x + 12, self.y + 18), (self.x + 12, self.y + 42), (self.x + 2, self.y + 48)] pygame.draw.polygon(surface, (80, 80, 120), wing_left) pygame.draw.polygon(surface, (120, 120, 160), wing_left, 2) # Détails aile gauche pygame.draw.line(surface, (150, 150, 180), (self.x + 6, self.y + 25), (self.x + 10, self.y + 38), 1) # Aile droite wing_right = [(self.x + 50, self.y + 28), (self.x + 38, self.y + 18), (self.x + 38, self.y + 42), (self.x + 48, self.y + 48)] pygame.draw.polygon(surface, (80, 80, 120), wing_right) pygame.draw.polygon(surface, (120, 120, 160), wing_right, 2) # Détails aile droite pygame.draw.line(surface, (150, 150, 180), (self.x + 44, self.y + 25), (self.x + 40, self.y + 38), 1) # Canons laser sur les côtés pygame.draw.rect(surface, (180, 180, 200), (self.x + 10, self.y + 38, 4, 10)) pygame.draw.rect(surface, (180, 180, 200), (self.x + 36, self.y + 38, 4, 10)) pygame.draw.rect(surface, (255, 200, 0), (self.x + 11, self.y + 46, 2, 2)) pygame.draw.rect(surface, (255, 200, 0), (self.x + 37, self.y + 46, 2, 2)) # Réacteurs avec flammes ultra-détaillées flame_height = 18 + (self.flame_offset // 2) for engine_x in [self.x + 13, self.x + 33]: # Lueur du réacteur engine_glow = pygame.Surface((12, 12), pygame.SRCALPHA) pygame.draw.circle(engine_glow, (255, 150, 0, 150), (6, 6), 6) surface.blit(engine_glow, (engine_x - 2, self.y + 43)) # Flamme principale (couches multiples) for i, (color, width, offset) in enumerate([ ((255, 80, 0), 8, 0), ((255, 150, 0), 6, 3), ((255, 200, 50), 4, 6), ((255, 255, 150), 2, 9) ]): if offset < flame_height: flame_points = [ (engine_x + width // 2, self.y + 45 + offset), (engine_x + width, self.y + 45 + offset + 4), (engine_x + width // 2, self.y + 45 + min(flame_height, offset + 8)), (engine_x, self.y + 45 + offset + 4) ] pygame.draw.polygon(surface, color, flame_points) # Particules d'étincelles if self.flame_offset % 3 == 0: for _ in range(2): spark_y = self.y + 48 + random.randint(0, flame_height) spark_x = engine_x + 4 + random.randint(-2, 2) pygame.draw.circle(surface, (255, 255, 200), (spark_x, spark_y), 1) # Détails supplémentaires (rivets, panneaux) for rivet_x, rivet_y in [(15, 25), (35, 25), (15, 35), (35, 35)]: pygame.draw.circle(surface, (100, 100, 120), (self.x + rivet_x, self.y + rivet_y), 2) pygame.draw.circle(surface, (150, 150, 170), (self.x + rivet_x, self.y + rivet_y), 1) # Bande lumineuse sur les côtés pygame.draw.line(surface, (0, 255, 255), (self.x + 12, self.y + 30), (self.x + 12, self.y + 40), 1) pygame.draw.line(surface, (0, 255, 255), (self.x + 38, self.y + 30), (self.x + 38, self.y + 40), 1) class Bullet: def __init__(self, x, y): self.x = x self.y = y self.speed = 8 self.w = 4 self.h = 10 self.glow_phase = 0 def update(self): self.y -= self.speed self.glow_phase = (self.glow_phase + 0.3) % 6.28 def draw(self, surface): import math # Lueur pulsante glow_size = int(8 + 4 * abs(math.sin(self.glow_phase))) glow = pygame.Surface((glow_size * 2, glow_size * 2), pygame.SRCALPHA) pygame.draw.circle(glow, (255, 255, 0, 100), (glow_size, glow_size), glow_size) surface.blit(glow, (self.x - glow_size + 2, self.y - glow_size + 5)) # Corps du projectile (effet d'énergie) pygame.draw.rect(surface, (255, 255, 0), (self.x, self.y, self.w, self.h)) pygame.draw.rect(surface, (255, 255, 150), (self.x + 1, self.y + 2, self.w - 2, self.h - 4)) # Traînée énergétique multicouche for i in range(3): alpha = 180 - i * 60 trail_h = 8 + i * 4 trail_s = pygame.Surface((self.w + 2, trail_h), pygame.SRCALPHA) pygame.draw.rect(trail_s, (255, 255, 0, alpha), (0, 0, self.w + 2, trail_h)) surface.blit(trail_s, (self.x - 1, self.y + self.h + i * 4)) # Particules d'étincelles if random.random() < 0.3: for _ in range(2): spark_x = self.x + random.randint(-2, self.w + 2) spark_y = self.y + random.randint(0, self.h) pygame.draw.circle(surface, (255, 255, 200), (spark_x, spark_y), 1) class Enemy: def __init__(self, level): self.x = random.randint(0, WIDTH - 50) self.y = -50 self.w = 50 self.h = 50 self.type = "fast" if random.random() > 0.7 else "normal" self.speed = (1.8 + level * 0.15) if self.type == "fast" else (1.2 + level * 0.1) self.color = MAGENTA if self.type == "fast" else RED self.tentacle_offset = 0 self.eye_blink = 0 self.rotation = 0 def update(self): self.y += self.speed self.tentacle_offset = (self.tentacle_offset + 1) % 20 self.eye_blink = (self.eye_blink + 1) % 60 self.rotation = (self.rotation + 2) % 360 def draw(self, surface): import math # Ombre portée shadow = pygame.Surface((self.w + 10, self.h + 10), pygame.SRCALPHA) pygame.draw.ellipse(shadow, (0, 0, 0, 60), (0, 0, self.w + 10, self.h + 10)) surface.blit(shadow, (self.x - 5, self.y + 8)) # Aura pulsante pulse = abs(math.sin(self.tentacle_offset / 3)) aura_size = int(70 + 10 * pulse) aura = pygame.Surface((aura_size, aura_size), pygame.SRCALPHA) aura_alpha = int(40 + 20 * pulse) pygame.draw.circle(aura, (*self.color, aura_alpha), (aura_size // 2, aura_size // 2), aura_size // 2) surface.blit(aura, (self.x + self.w // 2 - aura_size // 2, self.y + self.h // 2 - aura_size // 2)) # Corps principal de l'alien avec texture body_color = (150, 0, 255) if self.type == "fast" else (255, 50, 50) dark_body = tuple(max(0, c - 50) for c in body_color) # Corps multicouches pygame.draw.ellipse(surface, dark_body, (self.x + 3, self.y + 12, 44, 38)) pygame.draw.ellipse(surface, body_color, (self.x + 5, self.y + 10, 40, 35)) # Motifs organiques sur le corps for i in range(3): pattern_y = self.y + 15 + i * 10 pygame.draw.ellipse(surface, tuple(min(255, c + 30) for c in body_color), (self.x + 12 + i * 2, pattern_y, 26 - i * 4, 8)) # Tête/Dôme avec reflets pygame.draw.ellipse(surface, tuple(max(0, c - 30) for c in self.color), (self.x + 8, self.y + 2, 34, 28)) pygame.draw.ellipse(surface, self.color, (self.x + 10, self.y, 30, 25)) # Reflet brillant pygame.draw.ellipse(surface, tuple(min(255, c + 100) for c in self.color), (self.x + 14, self.y + 3, 12, 8)) # Yeux détaillés avec animations eye_open = self.eye_blink < 55 if eye_open: # Yeux blancs avec ombres pygame.draw.circle(surface, (200, 200, 200), (self.x + 18, self.y + 13), 7) pygame.draw.circle(surface, WHITE, (self.x + 18, self.y + 12), 6) pygame.draw.circle(surface, (200, 200, 200), (self.x + 32, self.y + 13), 7) pygame.draw.circle(surface, WHITE, (self.x + 32, self.y + 12), 6) # Pupilles colorées avec lueur pupil_color = (200, 0, 200) if self.type == "fast" else (200, 0, 0) pygame.draw.circle(surface, pupil_color, (self.x + 18, self.y + 12), 4) pygame.draw.circle(surface, pupil_color, (self.x + 32, self.y + 12), 4) # Points lumineux dans les yeux pygame.draw.circle(surface, (255, 255, 255), (self.x + 19, self.y + 11), 2) pygame.draw.circle(surface, (255, 255, 255), (self.x + 33, self.y + 11), 2) else: # Yeux fermés stylisés pygame.draw.line(surface, (80, 80, 80), (self.x + 13, self.y + 12), (self.x + 23, self.y + 12), 3) pygame.draw.line(surface, (80, 80, 80), (self.x + 27, self.y + 12), (self.x + 37, self.y + 12), 3) # Tentacules animés avec détails tentacle_wave = abs(self.tentacle_offset - 10) / 10 num_tentacles = 4 if self.type == "fast" else 3 for i in range(num_tentacles): x_pos = self.x + 15 + i * 10 y_start = self.y + 40 # Ondulation des tentacules wave_offset = math.sin((self.tentacle_offset + i * 5) / 5) * 4 tentacle_points = [ (x_pos, y_start), (x_pos + wave_offset, y_start + 6), (x_pos - wave_offset / 2, y_start + 12), (x_pos + wave_offset / 3, y_start + 18) ] # Tentacule avec ombre shadow_points = [(p[0] + 2, p[1] + 2) for p in tentacle_points] pygame.draw.lines(surface, (0, 0, 0, 80), False, shadow_points, 4) # Tentacule principal pygame.draw.lines(surface, dark_body, False, tentacle_points, 5) pygame.draw.lines(surface, body_color, False, tentacle_points, 3) # Ventouses sur les tentacules for j in range(2): sucker_pos = tentacle_points[j + 1] pygame.draw.circle(surface, tuple(max(0, c - 40) for c in body_color), (int(sucker_pos[0]), int(sucker_pos[1])), 3) # Détails métalliques brillants (armure alien) armor_positions = [(25, 25), (20, 32), (30, 32)] for ax, ay in armor_positions: # Armure avec relief pygame.draw.circle(surface, (220, 220, 220), (self.x + ax, self.y + ay), 5) pygame.draw.circle(surface, (180, 180, 180), (self.x + ax, self.y + ay), 4) pygame.draw.circle(surface, (140, 140, 140), (self.x + ax, self.y + ay), 2) # Reflet pygame.draw.circle(surface, (255, 255, 255), (self.x + ax - 1, self.y + ay - 1), 1) # Particules énergétiques autour de l'alien (pour type rapide) if self.type == "fast" and random.random() < 0.3: for _ in range(2): px = self.x + random.randint(5, 45) py = self.y + random.randint(5, 45) pygame.draw.circle(surface, (255, 0, 255), (px, py), 2) class PowerUp: def __init__(self, x, y): self.x = x self.y = y self.size = 20 self.type = random.choice(["rapid", "health"]) self.speed = 2 self.pulse = 0 def update(self): self.y += self.speed self.pulse += 0.1 def draw(self, surface): color = GREEN if self.type == "rapid" else PINK pulse_val = abs((self.pulse * 180 / 3.14159) % 360 - 180) / 180 alpha = int(128 + 127 * pulse_val) s = pygame.Surface((self.size * 2, self.size * 2), pygame.SRCALPHA) pygame.draw.circle(s, (*color, alpha), (self.size, self.size), self.size) surface.blit(s, (self.x, self.y)) class Boss: def __init__(self, level): self.w = 150 self.h = 150 self.x = WIDTH // 2 - self.w // 2 self.y = -self.h self.target_y = 100 self.level = level self.boss_type = ((level // 10) - 1) % 5 # 5 types de boss différents self.health = 80 + level * 20 # Santé augmentée self.max_health = self.health self.speed = 2 + (level // 10) * 0.5 self.move_direction = 1 self.shoot_timer = 0 self.tentacle_offset = 0 self.eye_blink = 0 self.phase = "entering" self.bullets = [] self.special_timer = 0 # Caractéristiques selon le type self.set_boss_characteristics() def set_boss_characteristics(self): """Définir les caractéristiques visuelles et comportementales""" if self.boss_type == 0: # Boss Tentacules (violet) self.color1 = (100, 0, 150) self.color2 = (150, 0, 200) self.eye_color = (255, 0, 0) self.shoot_pattern = "triple" elif self.boss_type == 1: # Boss Cristal (cyan) self.color1 = (0, 150, 150) self.color2 = (0, 200, 200) self.eye_color = (0, 255, 255) self.shoot_pattern = "spiral" elif self.boss_type == 2: # Boss Armure (orange) self.color1 = (180, 80, 0) self.color2 = (220, 120, 0) self.eye_color = (255, 150, 0) self.shoot_pattern = "spread" elif self.boss_type == 3: # Boss Énergie (vert) self.color1 = (0, 120, 0) self.color2 = (0, 180, 0) self.eye_color = (0, 255, 0) self.shoot_pattern = "wave" else: # Boss Ultime (rouge foncé) self.color1 = (150, 0, 0) self.color2 = (200, 0, 0) self.eye_color = (255, 0, 100) self.shoot_pattern = "all" def update(self): self.tentacle_offset = (self.tentacle_offset + 1) % 20 self.eye_blink = (self.eye_blink + 1) % 80 self.shoot_timer += 1 self.special_timer += 1 # Phase d'entrée if self.phase == "entering": if self.y < self.target_y: self.y += self.speed else: self.phase = "fighting" # Phase de combat elif self.phase == "fighting": # Mouvement horizontal self.x += self.speed * self.move_direction if self.x <= 0 or self.x >= WIDTH - self.w: self.move_direction *= -1 # Patterns de tir selon le type de boss shoot_frequency = max(30, 60 - (self.level // 10) * 5) if self.shoot_timer % shoot_frequency == 0: self.shoot() # Attaque spéciale tous les 5 secondes if self.special_timer % 300 == 0 and self.boss_type >= 2: self.special_attack() # Mise à jour des balles du boss for bullet in self.bullets[:]: bullet.update() if bullet.y > HEIGHT or bullet.y < -20 or bullet.x < -20 or bullet.x > WIDTH + 20: self.bullets.remove(bullet) def shoot(self): """Tir selon le pattern du boss""" center_x = self.x + self.w // 2 center_y = self.y + self.h if self.shoot_pattern == "triple": # Tir triple standard self.bullets.append(BossBullet(center_x - 40, center_y, 0, 4)) self.bullets.append(BossBullet(center_x, center_y, 0, 4)) self.bullets.append(BossBullet(center_x + 40, center_y, 0, 4)) elif self.shoot_pattern == "spiral": # Tir en spirale import math for i in range(6): angle = (self.shoot_timer * 10 + i * 60) % 360 vx = math.cos(math.radians(angle)) * 2 vy = math.sin(math.radians(angle)) * 2 + 3 self.bullets.append(BossBullet(center_x, center_y, vx, vy)) elif self.shoot_pattern == "spread": # Tir en éventail for i in range(-2, 3): vx = i * 1.5 self.bullets.append(BossBullet(center_x + i * 20, center_y, vx, 4)) elif self.shoot_pattern == "wave": # Tir en vague for i in range(5): delay = i * 5 self.bullets.append(BossBullet(center_x + (i - 2) * 30, center_y, 0, 4 + (i % 2))) elif self.shoot_pattern == "all": # Combinaison de tous les patterns pattern = (self.shoot_timer // 60) % 4 if pattern == 0: self.shoot_pattern = "triple" self.shoot() elif pattern == 1: self.shoot_pattern = "spiral" self.shoot() elif pattern == 2: self.shoot_pattern = "spread" self.shoot() else: self.shoot_pattern = "wave" self.shoot() self.shoot_pattern = "all" def special_attack(self): """Attaque spéciale puissante""" center_x = self.x + self.w // 2 center_y = self.y + self.h # Cercle de projectiles import math for i in range(12): angle = i * 30 vx = math.cos(math.radians(angle)) * 3 vy = math.sin(math.radians(angle)) * 3 self.bullets.append(BossBullet(center_x, center_y, vx, vy, special=True)) def draw(self, surface): # Corps massif pygame.draw.ellipse(surface, self.color1, (self.x + 20, self.y + 40, 110, 90)) # Tête/Dôme principal pygame.draw.ellipse(surface, self.color2, (self.x + 30, self.y + 10, 90, 70)) # Effets visuels selon le type if self.boss_type == 1: # Cristal - ajouter des facettes for i in range(4): offset = i * 20 pygame.draw.polygon(surface, (255, 255, 255, 100), [ (self.x + 40 + offset, self.y + 20), (self.x + 50 + offset, self.y + 30), (self.x + 40 + offset, self.y + 40) ]) # Yeux gigantesques eye_open = self.eye_blink < 75 if eye_open: # Yeux blancs pygame.draw.circle(surface, WHITE, (self.x + 50, self.y + 40), 15) pygame.draw.circle(surface, WHITE, (self.x + 100, self.y + 40), 15) # Pupilles colorées selon le type pygame.draw.circle(surface, self.eye_color, (self.x + 50, self.y + 40), 8) pygame.draw.circle(surface, self.eye_color, (self.x + 100, self.y + 40), 8) # Reflets pygame.draw.circle(surface, WHITE, (self.x + 52, self.y + 38), 3) pygame.draw.circle(surface, WHITE, (self.x + 102, self.y + 38), 3) else: pygame.draw.line(surface, (80, 0, 100), (self.x + 35, self.y + 40), (self.x + 65, self.y + 40), 4) pygame.draw.line(surface, (80, 0, 100), (self.x + 85, self.y + 40), (self.x + 115, self.y + 40), 4) # Bouche menaçante pygame.draw.arc(surface, self.eye_color, (self.x + 50, self.y + 55, 50, 30), 0, 3.14159, 3) # Tentacules épais (style différent selon le type) tentacle_wave = abs(self.tentacle_offset - 10) / 5 num_tentacles = 8 if self.boss_type >= 3 else 6 tentacle_thickness = 8 if self.boss_type >= 2 else 6 for i in range(num_tentacles): x_pos = self.x + 15 + i * (120 // num_tentacles) y_start = self.y + 120 tentacle_points = [ (x_pos, y_start), (x_pos + 3 * tentacle_wave, y_start + 10), (x_pos - 3 * tentacle_wave, y_start + 20), (x_pos + 2 * tentacle_wave, y_start + 30) ] pygame.draw.lines(surface, self.color1, False, tentacle_points, tentacle_thickness) # Armure métallique (plus pour les boss avancés) armor_count = 3 + (self.boss_type * 2) for i in range(min(armor_count, 5)): for j in range(2): x_offset = i * (100 // armor_count) pygame.draw.circle(surface, (180, 180, 180), (self.x + 30 + x_offset, self.y + 70 + j * 25), 6) pygame.draw.circle(surface, (120, 120, 120), (self.x + 30 + x_offset, self.y + 70 + j * 25), 3) # Aura de boss (couleur selon le type) s = pygame.Surface((self.w + 40, self.h + 40), pygame.SRCALPHA) pygame.draw.ellipse(s, (*self.color2, 40), (0, 0, self.w + 40, self.h + 40)) surface.blit(s, (self.x - 20, self.y - 20)) # Barre de vie bar_width = self.w bar_height = 12 bar_x = self.x bar_y = self.y - 25 # Fond de la barre pygame.draw.rect(surface, (50, 50, 50), (bar_x, bar_y, bar_width, bar_height)) # Barre de vie actuelle health_width = int(bar_width * (self.health / self.max_health)) health_color = GREEN if self.health > self.max_health * 0.5 else (ORANGE if self.health > self.max_health * 0.25 else RED) pygame.draw.rect(surface, health_color, (bar_x, bar_y, health_width, bar_height)) # Bordure pygame.draw.rect(surface, WHITE, (bar_x, bar_y, bar_width, bar_height), 2) # Nom du boss selon le type boss_names = ["BOSS TENTACULES", "BOSS CRISTAL", "BOSS ARMURE", "BOSS ÉNERGIE", "BOSS ULTIME"] boss_text = font_medium.render(f"⚠️ {boss_names[self.boss_type]} ⚠️", True, self.eye_color) surface.blit(boss_text, (self.x + self.w // 2 - boss_text.get_width() // 2, self.y - 50)) # Dessiner les projectiles du boss for bullet in self.bullets: bullet.draw(surface) class BossBullet: def __init__(self, x, y, vx=0, vy=4, special=False): self.x = x self.y = y self.vx = vx self.vy = vy self.w = 10 if special else 8 self.h = 18 if special else 16 self.speed = 4 self.special = special def update(self): self.y += self.vy self.x += self.vx def draw(self, surface): # Projectile énergétique violet (ou spécial en rouge) color1 = (255, 0, 0) if self.special else (200, 0, 255) color2 = (255, 100, 100) if self.special else (255, 100, 255) pygame.draw.ellipse(surface, color1, (self.x, self.y, self.w, self.h)) pygame.draw.ellipse(surface, color2, (self.x + 2, self.y + 4, self.w - 4, self.h - 8)) # Traînée s = pygame.Surface((self.w + 4, self.h + 10), pygame.SRCALPHA) pygame.draw.ellipse(s, (*color1, 128), (0, 0, self.w + 4, self.h + 10)) surface.blit(s, (self.x - 2, self.y - 5)) class Particle: def __init__(self, x, y): self.x = x self.y = y self.vx = random.uniform(-4, 4) self.vy = random.uniform(-4, 4) self.life = 40 self.max_life = 40 hue = random.randint(10, 60) self.color = pygame.Color(0) self.color.hsla = (hue, 100, 50, 100) self.size = random.randint(2, 5) self.rotation = random.randint(0, 360) def update(self): self.x += self.vx self.y += self.vy self.vy += 0.2 # Gravité self.life -= 1 self.rotation = (self.rotation + 10) % 360 def draw(self, surface): if self.life > 0: alpha = int(255 * (self.life / self.max_life)) # Particule avec lueur glow = pygame.Surface((self.size * 4, self.size * 4), pygame.SRCALPHA) pygame.draw.circle(glow, (*self.color[:3], alpha // 3), (self.size * 2, self.size * 2), self.size * 2) surface.blit(glow, (self.x - self.size * 2, self.y - self.size * 2)) # Particule principale (forme variable) s = pygame.Surface((self.size * 2, self.size * 2), pygame.SRCALPHA) if self.size > 3: # Carré pour grandes particules pygame.draw.rect(s, (*self.color[:3], alpha), (0, 0, self.size * 2, self.size * 2)) else: # Cercle pour petites pygame.draw.circle(s, (*self.color[:3], alpha), (self.size, self.size), self.size) surface.blit(s, (self.x, self.y)) class Explosion: def __init__(self, x, y): self.x = x self.y = y self.frame = 0 self.max_frame = 25 def update(self): self.frame += 1 def draw(self, surface): if self.frame <= self.max_frame: progress = self.frame / self.max_frame # Plusieurs ondes d'explosion for wave in range(3): radius = int((self.frame - wave * 3) * 3) if radius > 0: alpha = int(255 * (1 - progress) * (1 - wave * 0.3)) # Onde extérieure s = pygame.Surface((radius * 2 + 20, radius * 2 + 20), pygame.SRCALPHA) colors = [(255, 100, 0), (255, 150, 0), (255, 200, 50)] pygame.draw.circle(s, (*colors[wave % 3], alpha), (radius + 10, radius + 10), radius, 4) surface.blit(s, (self.x - radius - 10, self.y - radius - 10)) # Flash central if self.frame < 8: flash_size = 15 - self.frame * 2 flash = pygame.Surface((flash_size * 2, flash_size * 2), pygame.SRCALPHA) pygame.draw.circle(flash, (255, 255, 255, 200), (flash_size, flash_size), flash_size) surface.blit(flash, (self.x - flash_size, self.y - flash_size)) class Game: def __init__(self): self.reset() self.load_leaderboard() def reset(self): self.player = Player() self.bullets = [] self.enemies = [] self.powerups = [] self.stars = [Star() for _ in range(100)] self.particles = [] self.explosions = [] self.score = 0 self.level = 1 self.health = 3 self.combo = 0 self.fire_rate = 400 self.last_shot = 0 self.game_over = False self.player_name = "" self.state = "menu" self.boss = None self.boss_active = False def handle_input(self, keys, events): if self.state == "menu": for event in events: if event.type == pygame.KEYDOWN: if event.key == pygame.K_RETURN: self.state = "playing" elif event.key == pygame.K_BACKSPACE: self.player_name = self.player_name[:-1] elif event.unicode.isprintable() and len(self.player_name) < 20: self.player_name += event.unicode elif self.state == "playing": self.player.update(keys) if keys[pygame.K_SPACE]: self.shoot() elif self.state == "game_over": for event in events: if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN: self.reset() def shoot(self): now = pygame.time.get_ticks() if now - self.last_shot > self.fire_rate: self.bullets.append(Bullet(self.player.x + 23, self.player.y)) self.last_shot = now def spawn_enemy(self): if not self.boss_active and random.random() < 0.02 + self.level * 0.003: self.enemies.append(Enemy(self.level)) def create_explosion(self, x, y): self.explosions.append(Explosion(x, y)) for _ in range(25): self.particles.append(Particle(x, y)) def update(self): if self.state != "playing": return # Mise à jour des étoiles for star in self.stars: star.update() # Mise à jour du boss if self.boss_active and self.boss: self.boss.update() # Collisions balles joueur - boss for bullet in self.bullets[:]: if (self.boss and bullet.x < self.boss.x + self.boss.w and bullet.x + bullet.w > self.boss.x and bullet.y < self.boss.y + self.boss.h and bullet.y + bullet.h > self.boss.y): if bullet in self.bullets: self.bullets.remove(bullet) self.boss.health -= 1 self.score += 5 self.create_explosion(bullet.x, bullet.y) if self.boss and self.boss.health <= 0: # Boss vaincu self.create_explosion(self.boss.x + self.boss.w // 2, self.boss.y + self.boss.h // 2) self.score += 500 self.health = min(self.health + 2, 5) self.boss_active = False self.boss = None self.level += 1 break # Collisions projectiles boss - joueur if self.boss: for bullet in self.boss.bullets[:]: if (self.player.x < bullet.x + bullet.w and self.player.x + self.player.w > bullet.x and self.player.y < bullet.y + bullet.h and self.player.y + self.player.h > bullet.y): if bullet in self.boss.bullets: self.boss.bullets.remove(bullet) self.health -= 1 self.combo = 0 self.create_explosion(self.player.x + self.player.w // 2, self.player.y + self.player.h // 2) if self.health <= 0: self.end_game() # Mise à jour des balles for bullet in self.bullets[:]: bullet.update() if bullet.y < -10: self.bullets.remove(bullet) # Spawn ennemis (pas pendant le boss) self.spawn_enemy() # Mise à jour des ennemis for enemy in self.enemies[:]: enemy.update() if enemy.y > HEIGHT + 50: self.enemies.remove(enemy) # Collisions balles-ennemis for bullet in self.bullets[:]: for enemy in self.enemies[:]: if (bullet.x < enemy.x + enemy.w and bullet.x + bullet.w > enemy.x and bullet.y < enemy.y + enemy.h and bullet.y + bullet.h > enemy.y): self.create_explosion(enemy.x + enemy.w // 2, enemy.y + enemy.h // 2) if bullet in self.bullets: self.bullets.remove(bullet) if enemy in self.enemies: self.enemies.remove(enemy) self.combo += 1 self.score += 10 * self.combo if random.random() < 0.25: self.powerups.append(PowerUp(enemy.x, enemy.y)) break # Collisions joueur-ennemis for enemy in self.enemies[:]: if (self.player.x < enemy.x + enemy.w and self.player.x + self.player.w > enemy.x and self.player.y < enemy.y + enemy.h and self.player.y + self.player.h > enemy.y): self.create_explosion(enemy.x + enemy.w // 2, enemy.y + enemy.h // 2) if enemy in self.enemies: self.enemies.remove(enemy) self.health -= 1 self.combo = 0 if self.health <= 0: self.end_game() # Mise à jour des power-ups for powerup in self.powerups[:]: powerup.update() if (self.player.x < powerup.x + powerup.size and self.player.x + self.player.w > powerup.x and self.player.y < powerup.y + powerup.size and self.player.y + self.player.h > powerup.y): if powerup.type == "rapid": self.fire_rate = max(100, self.fire_rate - 100) else: self.health = min(self.health + 1, 5) self.powerups.remove(powerup) elif powerup.y > HEIGHT: self.powerups.remove(powerup) # Mise à jour des particules for particle in self.particles[:]: particle.update() if particle.life <= 0: self.particles.remove(particle) # Mise à jour des explosions for explosion in self.explosions[:]: explosion.update() if explosion.frame > explosion.max_frame: self.explosions.remove(explosion) # Niveau suivant et spawn de boss if self.score > self.level * 1000 and not self.boss_active: self.level += 1 self.health = min(self.health + 1, 5) # Boss tous les 10 niveaux if self.level % 10 == 0: self.boss_active = True self.boss = Boss(self.level) self.enemies.clear() # Nettoyer les ennemis normaux def end_game(self): self.state = "game_over" self.save_score() def save_score(self): name = self.player_name if self.player_name else "Anonyme" score_data = {"name": name, "score": self.score, "level": self.level} if not hasattr(self, 'leaderboard'): self.leaderboard = [] self.leaderboard.append(score_data) self.leaderboard.sort(key=lambda x: x["score"], reverse=True) self.leaderboard = self.leaderboard[:10] try: with open("leaderboard.json", "w", encoding="utf-8") as f: json.dump(self.leaderboard, f, ensure_ascii=False, indent=2) except Exception as e: print(f"Erreur de sauvegarde: {e}") def load_leaderboard(self): try: if os.path.exists("leaderboard.json"): with open("leaderboard.json", "r", encoding="utf-8") as f: self.leaderboard = json.load(f) else: self.leaderboard = [] except Exception as e: print(f"Erreur de chargement: {e}") self.leaderboard = [] def draw(self): # Fond screen.fill((0, 4, 32)) # Étoiles for star in self.stars: star.draw(screen) if self.state == "menu": self.draw_menu() elif self.state == "playing": self.draw_game() elif self.state == "game_over": self.draw_game_over() pygame.display.flip() def draw_menu(self): title = font_large.render("🚀 SPACE SHOOTER", True, CYAN) screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 120)) subtitle = font_small.render("Défendez la Terre contre l'invasion alien!", True, WHITE) screen.blit(subtitle, (WIDTH // 2 - subtitle.get_width() // 2, 200)) prompt = font_medium.render("Entrez votre nom:", True, WHITE) screen.blit(prompt, (WIDTH // 2 - prompt.get_width() // 2, 280)) # Zone de texte pygame.draw.rect(screen, BLUE, (WIDTH // 2 - 150, 330, 300, 40), 2) name_text = font_medium.render(self.player_name, True, WHITE) screen.blit(name_text, (WIDTH // 2 - name_text.get_width() // 2, 335)) # Contrôles controls_title = font_medium.render("🎮 CONTROLES", True, YELLOW) screen.blit(controls_title, (WIDTH // 2 - controls_title.get_width() // 2, 410)) controls = [ "← → ↑ ↓ : Déplacer le vaisseau", "ESPACE : Tirer", "💚 Power-up vert : Tir rapide", "💗 Power-up rose : Vie supplémentaire" ] for i, control in enumerate(controls): text = font_small.render(control, True, WHITE) screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 450 + i * 30)) start = font_medium.render("Appuyez sur ENTREE pour commencer", True, GREEN) screen.blit(start, (WIDTH // 2 - start.get_width() // 2, 600)) # Leaderboard lead_title = font_medium.render("🏆 LEADERBOARD", True, YELLOW) screen.blit(lead_title, (WIDTH // 2 - lead_title.get_width() // 2, 660)) for i, entry in enumerate(self.leaderboard[:5]): medal = ["🥇", "🥈", "🥉", "", ""][i] text = f"{medal} {entry['name']} - {entry['score']} pts (Niv. {entry['level']})" score_text = font_small.render(text, True, WHITE) screen.blit(score_text, (WIDTH // 2 - score_text.get_width() // 2, 700 + i * 30)) def draw_game(self): # Dessiner tous les éléments du jeu self.player.draw(screen) for bullet in self.bullets: bullet.draw(screen) for enemy in self.enemies: enemy.draw(screen) for powerup in self.powerups: powerup.draw(screen) for particle in self.particles: particle.draw(screen) for explosion in self.explosions: explosion.draw(screen) # Dessiner le boss if self.boss_active and self.boss: self.boss.draw(screen) # Vies for i in range(self.health): heart = font_medium.render("❤️", True, RED) screen.blit(heart, (10 + i * 30, 10)) # Combo if self.combo > 1: combo_text = font_medium.render(f"COMBO x{self.combo}!", True, YELLOW) screen.blit(combo_text, (WIDTH - combo_text.get_width() - 10, 10)) # Score et niveau score_text = font_medium.render(f"Score: {self.score}", True, GREEN) screen.blit(score_text, (10, HEIGHT - 40)) level_text = font_medium.render(f"Niveau: {self.level}", True, ORANGE) screen.blit(level_text, (WIDTH - level_text.get_width() - 10, HEIGHT - 40)) # Avertissement de boss if self.level % 10 == 9 and not self.boss_active: warning = font_large.render("⚠️ BOSS IMMINENT! ⚠️", True, RED) screen.blit(warning, (WIDTH // 2 - warning.get_width() // 2, HEIGHT // 2)) def draw_game_over(self): # Dessiner le fond du jeu (flouté) self.draw_game() # Overlay sombre overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) overlay.fill((0, 0, 0, 200)) screen.blit(overlay, (0, 0)) # Game Over game_over_text = font_large.render("GAME OVER", True, RED) screen.blit(game_over_text, (WIDTH // 2 - game_over_text.get_width() // 2, HEIGHT // 2 - 150)) final_score = font_medium.render(f"Score Final: {self.score}", True, CYAN) screen.blit(final_score, (WIDTH // 2 - final_score.get_width() // 2, HEIGHT // 2 - 50)) final_level = font_medium.render(f"Niveau Atteint: {self.level}", True, ORANGE) screen.blit(final_level, (WIDTH // 2 - final_level.get_width() // 2, HEIGHT // 2)) restart = font_small.render("Appuyez sur ENTREE pour rejouer", True, GREEN) screen.blit(restart, (WIDTH // 2 - restart.get_width() // 2, HEIGHT // 2 + 100)) def main(): game = Game() running = True while running: clock.tick(FPS) events = pygame.event.get() for event in events: if event.type == pygame.QUIT: running = False keys = pygame.key.get_pressed() game.handle_input(keys, events) game.update() game.draw() pygame.quit() if __name__ == "__main__": main()