Advertisement
Guest User

Untitled

a guest
Jun 9th, 2025
6
0
4 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 37.67 KB | None | 0 0
  1. import pygame
  2. import numpy as np
  3. from noise import pnoise3
  4. import math
  5. import random
  6. from math import floor
  7.  
  8. # --- Constants ---
  9. SCREEN_WIDTH = 1200
  10. SCREEN_HEIGHT = 800
  11. BACKGROUND_COLOR = (135, 206, 235)  # Sky Blue
  12. GROUND_COLOR_LOW = (30, 100, 30)    # Darker forest green for lower terrain
  13. GROUND_COLOR_HIGH = (100, 160, 60)  # Lighter green for higher terrain
  14. GROUND_COLOR_EARTH = (139, 69, 19)  # Brown for lowest terrain
  15. TANK_WIDTH = 40
  16. TANK_HEIGHT = 20
  17. TURRET_WIDTH = 5
  18. TURRET_LENGTH = 25
  19. GRAVITY = 0.2
  20. EXPLOSION_DURATION = 30  # Frames
  21. TANK_MOVE_SPEED = 2
  22. MAX_FUEL = 100
  23. NOISE_UPDATE_INTERVAL = 10  # Update noise every x frames
  24. EXPLOSION_BUFFER = 10  # Extra pixels to update around explosion
  25. MENU_BG_COLOR = (50, 50, 50, 200)  # Semi-transparent dark gray
  26. BUTTON_COLOR = (100, 100, 100)     # Gray for buttons
  27. BUTTON_HOVER_COLOR = (150, 150, 150)  # Lighter gray for hover
  28. TEXT_COLOR = (255, 255, 255)       # White text
  29. WIND_MIN = -0.1  # Minimum wind speed (negative = left)
  30. WIND_MAX = 0.1   # Maximum wind speed (positive = right)
  31. BONUS_ITEM_RADIUS = 10
  32. BONUS_ITEM_POINTS = 50
  33. BONUS_ITEM_COLOR = (255, 215, 0)  # Gold
  34. SMOKE_DURATION = 120  # Frames for smoke effect
  35.  
  36. # --- Weapon Definitions ---
  37. WEAPONS = [
  38.     {
  39.         "name": "Standard Shell",
  40.         "explosion_radius": 30,
  41.         "crater_multiplier": 1.0,
  42.         "gravity_multiplier": 1.0,
  43.         "wind_multiplier": 1.0,
  44.         "weight": 1.0,
  45.         "warheads": 1,
  46.         "special": None,
  47.         "color": (0, 0, 0),
  48.         "cost": 0,
  49.         "purchased": True
  50.     },
  51.     {
  52.         "name": "Big Bomb",
  53.         "explosion_radius": 70,
  54.         "crater_multiplier": 1.2,
  55.         "gravity_multiplier": 1.2,
  56.         "wind_multiplier": 1.2,
  57.         "weight": 1.5,
  58.         "warheads": 1,
  59.         "special": None,
  60.         "color": (50, 50, 50),
  61.         "cost": 100,
  62.         "purchased": False
  63.     },
  64.     {
  65.         "name": "Digger",
  66.         "explosion_radius": 20,
  67.         "crater_multiplier": 2.5,
  68.         "gravity_multiplier": 1.0,
  69.         "wind_multiplier": 0.8,
  70.         "weight": 1.0,
  71.         "warheads": 1,
  72.         "special": "piercing",
  73.         "color": (139, 69, 19),
  74.         "cost": 80,
  75.         "purchased": False
  76.     },
  77.     {
  78.         "name": "Cluster Bomb",
  79.         "explosion_radius": 25,
  80.         "crater_multiplier": 1.0,
  81.         "gravity_multiplier": 1.0,
  82.         "wind_multiplier": 1.0,
  83.         "weight": 1.2,
  84.         "warheads": 3,
  85.         "special": None,
  86.         "color": (200, 0, 0),
  87.         "cost": 150,
  88.         "purchased": False
  89.     },
  90.     {
  91.         "name": "Bouncer",
  92.         "explosion_radius": 40,
  93.         "crater_multiplier": 1.0,
  94.         "gravity_multiplier": 0.8,
  95.         "wind_multiplier": 1.5,
  96.         "weight": 0.8,
  97.         "warheads": 1,
  98.         "special": "bouncing",
  99.         "color": (0, 200, 0),
  100.         "cost": 120,
  101.         "purchased": False
  102.     },
  103.     {
  104.         "name": "Homing Missile",
  105.         "explosion_radius": 35,
  106.         "crater_multiplier": 1.0,
  107.         "gravity_multiplier": 0.9,
  108.         "wind_multiplier": 0.7,
  109.         "weight": 0.9,
  110.         "warheads": 1,
  111.         "special": "homing",
  112.         "color": (255, 0, 255),
  113.         "cost": 200,
  114.         "purchased": False
  115.     },
  116.     {
  117.         "name": "Fragmentation Shell",
  118.         "explosion_radius": 30,
  119.         "crater_multiplier": 1.0,
  120.         "gravity_multiplier": 1.0,
  121.         "wind_multiplier": 1.0,
  122.         "weight": 1.0,
  123.         "warheads": 1,
  124.         "special": "fragmentation",
  125.         "color": (255, 165, 0),
  126.         "cost": 180,
  127.         "purchased": False
  128.     },
  129.     {
  130.         "name": "Smoke Bomb",
  131.         "explosion_radius": 100,
  132.         "crater_multiplier": 0.0,
  133.         "gravity_multiplier": 1.0,
  134.         "wind_multiplier": 1.0,
  135.         "weight": 1.0,
  136.         "warheads": 1,
  137.         "special": "smoke",
  138.         "color": (128, 128, 128),
  139.         "cost": 90,
  140.         "purchased": False
  141.     }
  142. ]
  143.  
  144. # --- Game Setup ---
  145. pygame.init()
  146. screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
  147. pygame.display.set_caption("Python Scorched Earth - A/D to Move, W for Weapon, Arrows for Aim, M for Menu")
  148. font = pygame.font.SysFont("consolas", 20)
  149. game_over_font = pygame.font.SysFont("consolas", 72)
  150. menu_font = pygame.font.SysFont("consolas", 30)
  151. clock = pygame.time.Clock()
  152.  
  153. # --- Perlin Noise Cloud Setup ---
  154. noise_width, noise_height = 200, 150
  155. scale = 50.0
  156. octaves = 4
  157. persistence = 0.5
  158. lacunarity = 2.0
  159. cloud_speed = 0.03
  160. morph_speed = 0.03
  161.  
  162. x = np.linspace(0, noise_width / scale, noise_width)
  163. y = np.linspace(0, noise_height / scale, noise_height)
  164. x_grid, y_grid = np.meshgrid(x, y)
  165.  
  166. vectorized_pnoise3 = np.vectorize(
  167.     lambda x, y, z: pnoise3(
  168.         x, y, z,
  169.         octaves=octaves,
  170.         persistence=persistence,
  171.         lacunarity=lacunarity,
  172.         repeatx=int(noise_width / scale),
  173.         repeaty=int(noise_height / scale),
  174.         repeatz=9999999
  175.     )
  176. )
  177.  
  178. cloud_surface = pygame.Surface((noise_width, noise_height), pygame.SRCALPHA)
  179. cached_noise_array = None
  180. last_noise_update = -NOISE_UPDATE_INTERVAL
  181.  
  182. def generate_noise(offset_x, time):
  183.     x_shifted = (x_grid + offset_x / scale) % (noise_width / scale)
  184.     z = time * morph_speed
  185.     noise_array = vectorized_pnoise3(x_shifted, y_grid, z)
  186.     return ((noise_array + 0.5) * 255).T
  187.  
  188. # --- Terrain Texture Setup ---
  189. texture_surface = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
  190. texture_scale = 0.1
  191. texture_octaves = 6
  192. texture_persistence = 0.6
  193. texture_lacunarity = 2.0
  194.  
  195. def generate_terrain_texture():
  196.     texture_array = np.zeros((SCREEN_HEIGHT, SCREEN_WIDTH), dtype=np.float32)
  197.     for y in range(SCREEN_HEIGHT):
  198.         for x in range(SCREEN_WIDTH):
  199.             noise_value = pnoise3(x * texture_scale, y * texture_scale, 0,
  200.                                   octaves=texture_octaves,
  201.                                   persistence=texture_persistence,
  202.                                   lacunarity=texture_lacunarity)
  203.             texture_array[y, x] = (noise_value + 1.0) * 25
  204.     return texture_array
  205.  
  206. # --- Helper Functions ---
  207. def generate_terrain(width, height):
  208.     terrain = []
  209.     scale = 0.005
  210.     base_height = height * 0.75
  211.     amplitude = 500
  212.     for x in range(width):
  213.         noise_value = pnoise3(x * scale, 0, 0, octaves=4, persistence=0.5, lacunarity=2.0)
  214.         y = base_height + noise_value * amplitude
  215.         terrain.append(int(max(0, min(height, y))))
  216.     return terrain
  217.  
  218. def get_terrain_y_and_angle(x, terrain_points):
  219.     x = int(max(0, min(len(terrain_points) - 2, x)))
  220.     y1 = terrain_points[x]
  221.     y2 = terrain_points[x+1]
  222.     angle = math.atan2(y1 - y2, 1)
  223.     return y1, angle
  224.  
  225. def interpolate_color(height, max_height):
  226.     if height > max_height * 0.9:
  227.         t = (height - max_height * 0.9) / (max_height * 0.1)
  228.         r = int(GROUND_COLOR_LOW[0] + t * (GROUND_COLOR_HIGH[0] - GROUND_COLOR_LOW[0]))
  229.         g = int(GROUND_COLOR_LOW[1] + t * (GROUND_COLOR_HIGH[1] - GROUND_COLOR_LOW[1]))
  230.         b = int(GROUND_COLOR_LOW[2] + t * (GROUND_COLOR_HIGH[2] - GROUND_COLOR_LOW[2]))
  231.     else:
  232.         t = height / (max_height * 0.9)
  233.         r = int(GROUND_COLOR_EARTH[0] + t * (GROUND_COLOR_LOW[0] - GROUND_COLOR_EARTH[0]))
  234.         g = int(GROUND_COLOR_EARTH[1] + t * (GROUND_COLOR_LOW[1] - GROUND_COLOR_EARTH[1]))
  235.         b = int(GROUND_COLOR_EARTH[2] + t * (GROUND_COLOR_LOW[2] - GROUND_COLOR_EARTH[2]))
  236.     return (r, g, b)
  237.  
  238. def generate_terrain_surface(terrain_points, texture_array):
  239.     terrain_surface = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
  240.     max_height = max(terrain_points)
  241.     for x in range(SCREEN_WIDTH):
  242.         y_top = int(terrain_points[x])
  243.         for y in range(y_top, SCREEN_HEIGHT):
  244.             base_color = interpolate_color(y_top, max_height)
  245.             texture_value = texture_array[y, x]
  246.             r = int(max(0, min(255, base_color[0] + texture_value * 0.5)))
  247.             g = int(max(0, min(255, base_color[1] + texture_value * 0.5)))
  248.             b = int(max(0, min(255, base_color[2] + texture_value * 0.5)))
  249.             terrain_surface.set_at((x, y), (r, g, b, 255))
  250.     return terrain_surface
  251.  
  252. def update_terrain_surface(terrain_surface, terrain_points, texture_array, px, radius):
  253.     max_height = max(terrain_points)
  254.     x_start = max(0, px - radius - EXPLOSION_BUFFER)
  255.     x_end = min(SCREEN_WIDTH, px + radius + EXPLOSION_BUFFER)
  256.     y_min = min(terrain_points[x_start:x_end]) if x_end > x_start else terrain_points[px]
  257.     y_min = max(0, y_min - EXPLOSION_BUFFER)
  258.     region_width = x_end - x_start
  259.     region_height = SCREEN_HEIGHT - y_min
  260.     region_surface = pygame.Surface((region_width, region_height), pygame.SRCALPHA)
  261.     for x in range(x_start, x_end):
  262.         y_top = int(terrain_points[x])
  263.         for y in range(y_min, SCREEN_HEIGHT):
  264.             y_region = y - y_min
  265.             if y >= y_top:
  266.                 base_color = interpolate_color(y_top, max_height)
  267.                 texture_value = texture_array[y, x]
  268.                 r = int(max(0, min(255, base_color[0] + texture_value * 0.5)))
  269.                 g = int(max(0, min(255, base_color[1] + texture_value * 0.5)))
  270.                 b = int(max(0, min(255, base_color[2] + texture_value * 0.5)))
  271.                 region_surface.set_at((x - x_start, y_region), (r, g, b, 255))
  272.             else:
  273.                 region_surface.set_at((x - x_start, y_region), (0, 0, 0, 0))
  274.     terrain_surface.blit(region_surface, (x_start, y_min))
  275.     return terrain_surface
  276.  
  277. # --- Bonus Item Class ---
  278. class BonusItem:
  279.     def __init__(self, x, y, points):
  280.         self.x = x
  281.         self.y = y
  282.         self.points = points
  283.         self.radius = BONUS_ITEM_RADIUS
  284.         self.collected = False
  285.  
  286.     def draw(self, surface):
  287.         if not self.collected:
  288.             pygame.draw.circle(surface, BONUS_ITEM_COLOR, (int(self.x), int(self.y)), self.radius)
  289.             pygame.draw.circle(surface, (255, 255, 255), (int(self.x), int(self.y)), self.radius // 2)
  290.  
  291.     def check_collision(self, explosion_x, explosion_y, explosion_radius):
  292.         if self.collected:
  293.             return False
  294.         distance = math.sqrt((self.x - explosion_x)**2 + (self.y - explosion_y)**2)
  295.         return distance <= explosion_radius + self.radius
  296.  
  297. # --- UI Classes ---
  298. class Button:
  299.     def __init__(self, text, x, y, width, height, action=None, weapon_index=None):
  300.         self.text = text
  301.         self.rect = pygame.Rect(x, y, width, height)
  302.         self.hovered = False
  303.         self.action = action
  304.         self.weapon_index = weapon_index
  305.  
  306.     def draw(self, surface):
  307.         color = BUTTON_HOVER_COLOR if self.hovered else BUTTON_COLOR
  308.         pygame.draw.rect(surface, color, self.rect)
  309.         text_surface = menu_font.render(self.text, True, TEXT_COLOR)
  310.         text_rect = text_surface.get_rect(center=self.rect.center)
  311.         surface.blit(text_surface, text_rect)
  312.  
  313.     def check_hover(self, mouse_pos):
  314.         self.hovered = self.rect.collidepoint(mouse_pos)
  315.         return self.hovered
  316.  
  317.     def check_click(self, mouse_pos):
  318.         return self.rect.collidepoint(mouse_pos)
  319.  
  320. class Menu:
  321.     def __init__(self):
  322.         self.active = False
  323.         self.mode = "main"
  324.         self.buttons = []
  325.         self.init_main_menu()
  326.  
  327.     def init_main_menu(self):
  328.         self.mode = "main"
  329.         button_width = 300
  330.         button_height = 60
  331.         button_spacing = 20
  332.         start_x = (SCREEN_WIDTH - button_width) // 2
  333.         start_y = (SCREEN_HEIGHT - (4 * button_height + 3 * button_spacing)) // 2
  334.         self.buttons = [
  335.             Button("Resume Game", start_x, start_y, button_width, button_height, action="resume"),
  336.             Button("Restart Game", start_x, start_y + button_height + button_spacing, button_width, button_height, action="restart"),
  337.             Button("Store", start_x, start_y + 2 * (button_height + button_spacing), button_width, button_height, action="store"),
  338.             Button("Quit Game", start_x, start_y + 3 * (button_height + button_spacing), button_width, button_height, action="quit")
  339.         ]
  340.  
  341.     def init_store_menu(self):
  342.         self.mode = "store"
  343.         button_width = 200
  344.         button_height = 40
  345.         button_spacing = 10
  346.         start_x = (SCREEN_WIDTH - button_width) // 2
  347.         start_y = 150
  348.         self.buttons = [Button("Back", start_x, SCREEN_HEIGHT - button_height - 20, button_width, button_height, action="back")]
  349.         for i, weapon in enumerate(WEAPONS):
  350.             if not weapon["purchased"]:
  351.                 self.buttons.append(Button(f"Buy {weapon['name']} ({weapon['cost']} pts)",
  352.                                         start_x, start_y + i * (button_height + button_spacing),
  353.                                         button_width, button_height, action="buy", weapon_index=i))
  354.  
  355.     def draw(self, surface, players):
  356.         if not self.active:
  357.             return
  358.         overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
  359.         overlay.fill(MENU_BG_COLOR)
  360.         surface.blit(overlay, (0, 0))
  361.         title = menu_font.render("Menu" if self.mode == "main" else "Store", True, TEXT_COLOR)
  362.         title_rect = title.get_rect(center=(SCREEN_WIDTH // 2, 100))
  363.         surface.blit(title, title_rect)
  364.         for button in self.buttons:
  365.             button.draw(surface)
  366.         for i, player in enumerate(players):
  367.             points_text = menu_font.render(f"Player {i+1} Points: {player.points}", True, player.color)
  368.             points_rect = points_text.get_rect(topleft=(20, 20 + i * 30))
  369.             surface.blit(points_text, points_rect)
  370.  
  371.     def handle_event(self, event, terrain_points, terrain_texture_array, terrain_surface, players, game_state, explosions, active_projectile, current_player_index, cloud_offset_x, cached_noise_array, last_noise_update, wind, bonus_items, smoke_effect):
  372.         if not self.active:
  373.             return (terrain_surface, terrain_points, game_state, explosions, active_projectile, current_player_index, cloud_offset_x, cached_noise_array, last_noise_update, wind, bonus_items, smoke_effect)
  374.         if event.type == pygame.MOUSEMOTION:
  375.             mouse_pos = event.pos
  376.             for button in self.buttons:
  377.                 button.check_hover(mouse_pos)
  378.         if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
  379.             mouse_pos = event.pos
  380.             for button in self.buttons:
  381.                 if button.check_click(mouse_pos) and button.action:
  382.                     if self.mode == "main":
  383.                         if button.action == "resume":
  384.                             self.active = False
  385.                         elif button.action == "restart":
  386.                             terrain_points = generate_terrain(SCREEN_WIDTH, SCREEN_HEIGHT)
  387.                             terrain_texture_array = generate_terrain_texture()
  388.                             terrain_surface = generate_terrain_surface(terrain_points, terrain_texture_array)
  389.                             bonus_items = generate_bonus_items(terrain_points)
  390.                             for p in players:
  391.                                 p.terrain = terrain_points
  392.                                 p.reset()
  393.                                 p.score = 0
  394.                                 p.points = 0
  395.                                 for weapon in WEAPONS:
  396.                                     if weapon["cost"] == 0:
  397.                                         weapon["purchased"] = True
  398.                                     else:
  399.                                         weapon["purchased"] = False
  400.                                 p.purchased_weapons = [i for i, w in enumerate(WEAPONS) if w["purchased"]]
  401.                             game_state = "AIMING"
  402.                             explosions = []
  403.                             active_projectile = None
  404.                             current_player_index = 0
  405.                             cloud_offset_x = 0
  406.                             cached_noise_array = None
  407.                             last_noise_update = -NOISE_UPDATE_INTERVAL
  408.                             wind = random.uniform(WIND_MIN, WIND_MAX)
  409.                             smoke_effect = None
  410.                             self.active = False
  411.                             return (terrain_surface, terrain_points, game_state, explosions, active_projectile, current_player_index, cloud_offset_x, cached_noise_array, last_noise_update, wind, bonus_items, smoke_effect)
  412.                         elif button.action == "store":
  413.                             self.init_store_menu()
  414.                         elif button.action == "quit":
  415.                             pygame.event.post(pygame.event.Event(pygame.QUIT))
  416.                     elif self.mode == "store":
  417.                         if button.action == "back":
  418.                             self.init_main_menu()
  419.                         elif button.action == "buy" and button.weapon_index is not None:
  420.                             weapon = WEAPONS[button.weapon_index]
  421.                             if not weapon["purchased"] and players[current_player_index].points >= weapon["cost"]:
  422.                                 players[current_player_index].points -= weapon["cost"]
  423.                                 weapon["purchased"] = True
  424.                                 players[current_player_index].purchased_weapons.append(button.weapon_index)
  425.                                 self.init_store_menu()
  426.         return (terrain_surface, terrain_points, game_state, explosions, active_projectile, current_player_index, cloud_offset_x, cached_noise_array, last_noise_update, wind, bonus_items, smoke_effect)
  427.  
  428. # --- Classes ---
  429. class Tank:
  430.     def __init__(self, x, color, terrain):
  431.         self.initial_x = x
  432.         self.color = color
  433.         self.terrain = terrain
  434.         self.angle = 90
  435.         self.power = 50
  436.         self.is_alive = True
  437.         self.score = 0
  438.         self.points = 0
  439.         self.current_weapon_index = 0
  440.         self.fuel = MAX_FUEL
  441.         self.purchased_weapons = [0]
  442.         self.reset()
  443.  
  444.     def reset(self):
  445.         self.x = self.initial_x
  446.         self.y, self.surface_angle = get_terrain_y_and_angle(self.x, self.terrain)
  447.         self.rect = pygame.Rect(self.x - TANK_WIDTH // 2, self.y - TANK_HEIGHT, TANK_WIDTH, TANK_HEIGHT)
  448.         self.is_alive = True
  449.         self.reset_fuel()
  450.         if self.x < SCREEN_WIDTH / 2:
  451.             self.angle = 45
  452.         else:
  453.             self.angle = 135
  454.  
  455.     def reset_fuel(self):
  456.         self.fuel = MAX_FUEL
  457.  
  458.     def move(self, direction):
  459.         if self.fuel > 0:
  460.             self.x += direction * TANK_MOVE_SPEED
  461.             self.x = max(TANK_WIDTH // 2, min(SCREEN_WIDTH - TANK_WIDTH // 2, self.x))
  462.             self.y, self.surface_angle = get_terrain_y_and_angle(self.x, self.terrain)
  463.             self.rect.centerx = self.x
  464.             self.rect.bottom = self.y
  465.             self.fuel -= 1
  466.  
  467.     def draw(self, surface):
  468.         if not self.is_alive:
  469.             return
  470.         tank_sprite_surface = pygame.Surface((TANK_WIDTH * 2, TANK_WIDTH * 2), pygame.SRCALPHA)
  471.         sprite_center_x, sprite_center_y = TANK_WIDTH, TANK_WIDTH
  472.         tread_color = (60, 60, 60)
  473.         body_color = self.color
  474.         pygame.draw.circle(tank_sprite_surface, tread_color, (sprite_center_x - TANK_WIDTH // 3, sprite_center_y + TANK_HEIGHT // 4), TANK_HEIGHT // 1.8)
  475.         pygame.draw.circle(tank_sprite_surface, tread_color, (sprite_center_x + TANK_WIDTH // 3, sprite_center_y + TANK_HEIGHT // 4), TANK_HEIGHT // 1.8)
  476.         body_rect = pygame.Rect(0, 0, TANK_WIDTH, TANK_HEIGHT)
  477.         body_rect.center = (sprite_center_x, sprite_center_y)
  478.         pygame.draw.rect(tank_sprite_surface, body_color, body_rect, border_radius=4)
  479.         rotated_surface = pygame.transform.rotate(tank_sprite_surface, math.degrees(self.surface_angle))
  480.         rotated_rect = rotated_surface.get_rect(center=(self.x, self.y - TANK_HEIGHT / 2))
  481.         surface.blit(rotated_surface, rotated_rect.topleft)
  482.         turret_base_x = self.x
  483.         turret_base_y = self.y - TANK_HEIGHT / 2
  484.         turret_end_x = turret_base_x + math.cos(math.radians(self.angle)) * TURRET_LENGTH
  485.         turret_end_y = turret_base_y - math.sin(math.radians(self.angle)) * TURRET_LENGTH
  486.         turret_color = (max(0, self.color[0] - 40), max(0, self.color[1] - 40), max(0, self.color[2] - 40))
  487.         pygame.draw.line(surface, turret_color, (turret_base_x, turret_base_y), (turret_end_x, turret_end_y), TURRET_WIDTH)
  488.  
  489.     def switch_weapon(self):
  490.         current_idx = self.purchased_weapons.index(self.current_weapon_index) if self.current_weapon_index in self.purchased_weapons else 0
  491.         self.current_weapon_index = self.purchased_weapons[(current_idx + 1) % len(self.purchased_weapons)]
  492.  
  493.     def fire(self):
  494.         weapon = WEAPONS[self.current_weapon_index]
  495.         angle_rad = math.radians(self.angle)
  496.         start_x = self.x + math.cos(angle_rad) * TURRET_LENGTH
  497.         start_y = self.y - TANK_HEIGHT / 2 - math.sin(angle_rad) * TURRET_LENGTH
  498.         return Projectile(start_x, start_y, self.power, angle_rad, weapon, self)
  499.  
  500. class Projectile:
  501.     def __init__(self, x, y, power, angle_rad, weapon_type, tank):
  502.         self.x = x
  503.         self.y = y
  504.         self.weapon_type = weapon_type
  505.         self.vx = math.cos(angle_rad) * power * 0.2
  506.         self.vy = -math.sin(angle_rad) * power * 0.2
  507.         self.path = []
  508.         self.time_alive = 0
  509.         self.bounced = False
  510.         self.tank = tank  # Reference to firing tank for homing
  511.  
  512.     def update(self, wind, terrain_points, players, current_player_index):
  513.         self.time_alive += 1 / 60
  514.         self.vy += GRAVITY * self.weapon_type["gravity_multiplier"] * self.weapon_type["weight"]
  515.         if self.weapon_type["special"] == "homing":
  516.             target = players[1 - current_player_index]
  517.             if target.is_alive:
  518.                 dx = target.x - self.x
  519.                 dy = target.y - self.y
  520.                 distance = math.sqrt(dx**2 + dy**2)
  521.                 if distance > 10:
  522.                     speed = math.sqrt(self.vx**2 + self.vy**2)
  523.                     target_vx = dx / distance * speed * 0.1
  524.                     target_vy = dy / distance * speed * 0.1
  525.                     self.vx += (target_vx - self.vx) * 0.05
  526.                     self.vy += (target_vy - self.vy) * 0.05
  527.         self.x += self.vx + wind * self.weapon_type["wind_multiplier"]
  528.         self.y += self.vy
  529.         px, py = int(self.x), int(self.y)
  530.         if self.weapon_type["special"] == "bouncing" and not self.bounced and 0 <= px < SCREEN_WIDTH and py >= terrain_points[px]:
  531.             self.vy = -self.vy * 0.5
  532.             self.vx *= 0.7
  533.             self.bounced = True
  534.         self.path.append((px, py))
  535.  
  536.     def draw(self, surface):
  537.         if len(self.path) > 2:
  538.             pygame.draw.lines(surface, (255, 255, 0), False, self.path, 2)
  539.         pygame.draw.circle(surface, self.weapon_type["color"], (int(self.x), int(self.y)), 5)
  540.  
  541. class Explosion:
  542.     def __init__(self, x, y, radius):
  543.         self.x = int(x)
  544.         self.y = int(y)
  545.         self.timer = EXPLOSION_DURATION
  546.         self.max_radius = radius
  547.  
  548.     def update(self):
  549.         self.timer -= 1
  550.         return self.timer > 0
  551.  
  552.     def draw(self, surface):
  553.         if self.timer <= 0:
  554.             return
  555.         progress = (EXPLOSION_DURATION - self.timer) / EXPLOSION_DURATION
  556.         current_radius = int(self.max_radius * progress)
  557.         color = (255, 165, 0) if self.timer % 4 < 2 else (255, 69, 0)
  558.         pygame.draw.circle(surface, color, (self.x, self.y), current_radius)
  559.  
  560. class SmokeEffect:
  561.     def __init__(self, x, y):
  562.         self.x = x
  563.         self.y = y
  564.         self.timer = SMOKE_DURATION
  565.         self.radius = 100
  566.  
  567.     def update(self):
  568.         self.timer -= 1
  569.         return self.timer > 0
  570.  
  571.     def draw(self, surface):
  572.         if self.timer <= 0:
  573.             return
  574.         progress = self.timer / SMOKE_DURATION
  575.         alpha = int(100 * progress)
  576.         smoke_surface = pygame.Surface((self.radius * 2, self.radius * 2), pygame.SRCALPHA)
  577.         pygame.draw.circle(smoke_surface, (128, 128, 128, alpha), (self.radius, self.radius), self.radius)
  578.         surface.blit(smoke_surface, (self.x - self.radius, self.y - self.radius))
  579.  
  580. def generate_bonus_items(terrain_points):
  581.     bonus_items = []
  582.     num_items = random.randint(3, 5)
  583.     for _ in range(num_items):
  584.         x = random.randint(50, SCREEN_WIDTH - 50)
  585.         y = terrain_points[x] - BONUS_ITEM_RADIUS - 5
  586.         bonus_items.append(BonusItem(x, y, BONUS_ITEM_POINTS))
  587.     return bonus_items
  588.  
  589. def switch_turn():
  590.     global current_player_index, wind
  591.     current_player_index = 1 - current_player_index
  592.     players[current_player_index].reset_fuel()
  593.     wind = random.uniform(WIND_MIN, WIND_MAX)
  594.  
  595. # --- Game State Variables ---
  596. terrain_points = generate_terrain(SCREEN_WIDTH, SCREEN_HEIGHT)
  597. terrain_texture_array = generate_terrain_texture()
  598. terrain_surface = generate_terrain_surface(terrain_points, terrain_texture_array)
  599. bonus_items = generate_bonus_items(terrain_points)
  600. player1 = Tank(random.randint(50, SCREEN_WIDTH // 2 - 100), (0, 0, 255), terrain_points)
  601. player2 = Tank(random.randint(SCREEN_WIDTH // 2 + 100, SCREEN_WIDTH - 50), (255, 0, 0), terrain_points)
  602. players = [player1, player2]
  603. current_player_index = 0
  604. active_projectile = None
  605. explosions = []
  606. game_state = "AIMING"
  607. cloud_offset_x = 0
  608. frame_count = 0
  609. menu = Menu()
  610. wind = random.uniform(WIND_MIN, WIND_MAX)
  611. smoke_effect = None
  612.  
  613. # --- Main Game Loop ---
  614. running = True
  615. while running:
  616.     for event in pygame.event.get():
  617.         if event.type == pygame.QUIT:
  618.             running = False
  619.         if event.type == pygame.KEYDOWN:
  620.             if event.key == pygame.K_m:
  621.                 menu.active = not menu.active
  622.                 if menu.active:
  623.                     menu.init_main_menu()
  624.             if not menu.active:
  625.                 if game_state == "GAMEOVER":
  626.                     if event.key == pygame.K_r:
  627.                         terrain_points = generate_terrain(SCREEN_WIDTH, SCREEN_HEIGHT)
  628.                         terrain_texture_array = generate_terrain_texture()
  629.                         terrain_surface = generate_terrain_surface(terrain_points, terrain_texture_array)
  630.                         bonus_items = generate_bonus_items(terrain_points)
  631.                         for p in players:
  632.                             p.terrain = terrain_points
  633.                             p.reset()
  634.                             p.score = 0
  635.                             p.points = 0
  636.                             for weapon in WEAPONS:
  637.                                 if weapon["cost"] == 0:
  638.                                     weapon["purchased"] = True
  639.                                 else:
  640.                                     weapon["purchased"] = False
  641.                                 p.purchased_weapons = [i for i, w in enumerate(WEAPONS) if w["purchased"]]
  642.                         current_player_index = 0
  643.                         active_projectile = None
  644.                         explosions = []
  645.                         game_state = "AIMING"
  646.                         cloud_offset_x = 0
  647.                         cached_noise_array = None
  648.                         last_noise_update = -NOISE_UPDATE_INTERVAL
  649.                         wind = random.uniform(WIND_MIN, WIND_MAX)
  650.                         smoke_effect = None
  651.                 if game_state == "AIMING":
  652.                     current_tank = players[current_player_index]
  653.                     if event.key == pygame.K_LEFT:
  654.                         current_tank.angle = min(180, current_tank.angle + 2)
  655.                     if event.key == pygame.K_RIGHT:
  656.                         current_tank.angle = max(0, current_tank.angle - 2)
  657.                     if event.key == pygame.K_UP:
  658.                         current_tank.power = min(100, current_tank.power + 2)
  659.                     if event.key == pygame.K_DOWN:
  660.                         current_tank.power = max(0, current_tank.power - 2)
  661.                     if event.key == pygame.K_w:
  662.                         current_tank.switch_weapon()
  663.                     if event.key == pygame.K_SPACE:
  664.                         active_projectile = current_tank.fire()
  665.                         game_state = "FIRING"
  666.         if menu.active:
  667.             terrain_surface, terrain_points, game_state, explosions, active_projectile, current_player_index, cloud_offset_x, cached_noise_array, last_noise_update, wind, bonus_items, smoke_effect = menu.handle_event(
  668.                 event, terrain_points, terrain_texture_array, terrain_surface, players, game_state, explosions, active_projectile, current_player_index, cloud_offset_x, cached_noise_array, last_noise_update, wind, bonus_items, smoke_effect
  669.             )
  670.             for p in players:
  671.                 p.terrain = terrain_points
  672.  
  673.     if not menu.active:
  674.         if game_state == "AIMING" and current_player_index == 0:
  675.             keys = pygame.key.get_pressed()
  676.             if keys[pygame.K_a]:
  677.                 players[0].move(-1)
  678.             if keys[pygame.K_d]:
  679.                 players[0].move(1)
  680.  
  681.         if current_player_index == 1 and game_state == "AIMING":
  682.             ai_tank = players[1]
  683.             player_tank = players[0]
  684.             ai_tank.current_weapon_index = random.choice(ai_tank.purchased_weapons)
  685.             dx = player_tank.x - ai_tank.x
  686.             dy = player_tank.y - ai_tank.y
  687.             if dx > 0:
  688.                 ai_tank.angle = random.randint(10, 80)
  689.             else:
  690.                 ai_tank.angle = random.randint(100, 170)
  691.             distance = math.sqrt(dx**2 + dy**2)
  692.             ai_tank.power = int(max(20, min(100, distance / 7)))
  693.             active_projectile = ai_tank.fire()
  694.             game_state = "FIRING"
  695.  
  696.         if game_state == "FIRING" and active_projectile:
  697.             active_projectile.update(wind, terrain_points, players, current_player_index)
  698.             px, py = int(active_projectile.x), int(active_projectile.y)
  699.             weapon_hit = active_projectile.weapon_type
  700.             should_explode = False
  701.             if weapon_hit["special"] == "piercing" and active_projectile.time_alive < 0.5:
  702.                 pass
  703.             elif 0 <= px < SCREEN_WIDTH and py >= terrain_points[px]:
  704.                 should_explode = True
  705.             if active_projectile:
  706.                 for i, tank in enumerate(players):
  707.                     if tank.is_alive and tank.rect.collidepoint(px, py):
  708.                         tank.is_alive = False
  709.                         players[1-i].score += 1
  710.                         should_explode = True
  711.                         break
  712.             if should_explode:
  713.                 explosions.append(Explosion(px, py, weapon_hit["explosion_radius"]))
  714.                 if weapon_hit["warheads"] > 1:
  715.                     for _ in range(weapon_hit["warheads"] - 1):
  716.                         offset_x = px + random.randint(-20, 20)
  717.                         offset_y = py + random.randint(-20, 20)
  718.                         if 0 <= offset_x < SCREEN_WIDTH:
  719.                             explosions.append(Explosion(offset_x, offset_y, weapon_hit["explosion_radius"] // 2))
  720.                 if weapon_hit["special"] == "fragmentation":
  721.                     for _ in range(4):
  722.                         angle = random.uniform(0, 2 * math.pi)
  723.                         power = random.uniform(20, 40)
  724.                         frag_weapon = {
  725.                             "explosion_radius": 15,
  726.                             "crater_multiplier": 0.5,
  727.                             "gravity_multiplier": 1.0,
  728.                             "wind_multiplier": 1.0,
  729.                             "weight": 0.5,
  730.                             "warheads": 1,
  731.                             "special": None,
  732.                             "color": (255, 165, 0)
  733.                         }
  734.                         frag_projectile = Projectile(px, py, power, angle, frag_weapon, active_projectile.tank)
  735.                         explosions.append(Explosion(px, py, frag_weapon["explosion_radius"]))
  736.                 if weapon_hit["special"] == "smoke":
  737.                     smoke_effect = SmokeEffect(px, py)
  738.                 if weapon_hit["crater_multiplier"] > 0:
  739.                     radius = weapon_hit["explosion_radius"]
  740.                     crater_mult = weapon_hit["crater_multiplier"]
  741.                     for i in range(max(0, px - radius), min(SCREEN_WIDTH, px + radius)):
  742.                         dist = abs(px - i)
  743.                         if dist < radius:
  744.                             crater_depth = math.sqrt(radius**2 - dist**2) * crater_mult
  745.                             terrain_points[i] = int(min(SCREEN_HEIGHT, terrain_points[i] + crater_depth))
  746.                     terrain_surface = update_terrain_surface(terrain_surface, terrain_points, terrain_texture_array, px, radius)
  747.                 for p in players:
  748.                     p.terrain = terrain_points
  749.                 for item in bonus_items:
  750.                     if item.check_collision(px, py, weapon_hit["explosion_radius"]):
  751.                         item.collected = True
  752.                         players[current_player_index].points += item.points
  753.                 active_projectile = None
  754.                 game_state = "EXPLODING"
  755.             if active_projectile and not (0 <= px < SCREEN_WIDTH and 0 <= py < SCREEN_HEIGHT + 200):
  756.                 active_projectile = None
  757.                 game_state = "AIMING"
  758.                 switch_turn()
  759.  
  760.         if game_state == "EXPLODING":
  761.             for explosion in explosions[:]:
  762.                 if not explosion.update():
  763.                     explosions.remove(explosion)
  764.             if smoke_effect and not smoke_effect.update():
  765.                 smoke_effect = None
  766.             if not explosions:
  767.                 for tank in players:
  768.                     if tank.is_alive:
  769.                         tank.y, tank.surface_angle = get_terrain_y_and_angle(tank.x, terrain_points)
  770.                         tank.rect.bottom = tank.y
  771.                 if not player1.is_alive or not player2.is_alive:
  772.                     game_state = "GAMEOVER"
  773.                 else:
  774.                     game_state = "AIMING"
  775.                     switch_turn()
  776.  
  777.         cloud_offset_x = (cloud_offset_x + cloud_speed) % (SCREEN_WIDTH * 2)
  778.         time = pygame.time.get_ticks() / 1000.0
  779.         if frame_count - last_noise_update >= NOISE_UPDATE_INTERVAL:
  780.             cached_noise_array = generate_noise(cloud_offset_x, time)
  781.             last_noise_update = frame_count
  782.         if cached_noise_array is not None:
  783.             pixel_array = pygame.surfarray.pixels3d(cloud_surface)
  784.             alpha_array = pygame.surfarray.pixels_alpha(cloud_surface)
  785.             mask = cached_noise_array > 0
  786.             pixel_array[mask] = [255, 255, 255]
  787.             pixel_array[~mask] = [0, 0, 0]
  788.             alpha_array[:] = np.clip((cached_noise_array - 50) * 2, 0, 255).astype(np.uint8)
  789.             del pixel_array
  790.             del alpha_array
  791.             scaled_cloud_surface = pygame.transform.scale(cloud_surface, (SCREEN_WIDTH, SCREEN_HEIGHT))
  792.         else:
  793.             scaled_cloud_surface = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
  794.  
  795.     # Drawing
  796.     screen.fill(BACKGROUND_COLOR)
  797.     screen.blit(scaled_cloud_surface, (0, 0))
  798.     screen.blit(terrain_surface, (0, 0))
  799.     pygame.draw.lines(screen, (20, 80, 20), False, [(x, y) for x, y in enumerate(terrain_points)], 2)
  800.  
  801.     for item in bonus_items:
  802.         item.draw(screen)
  803.     for player in players:
  804.         player.draw(screen)
  805.     if active_projectile:
  806.         active_projectile.draw(screen)
  807.     for explosion in explosions:
  808.         explosion.draw(screen)
  809.     if smoke_effect:
  810.         smoke_effect.draw(screen)
  811.  
  812.     if game_state != "GAMEOVER" and not menu.active:
  813.         current_tank = players[current_player_index]
  814.         weapon = WEAPONS[current_tank.current_weapon_index]
  815.         angle_text = font.render(f"Angle: {current_tank.angle:.0f}", True, (0,0,0))
  816.         power_text = font.render(f"Power: {current_tank.power:.0f}", True, (0,0,0))
  817.         weapon_text = font.render(f"Weapon: {weapon['name']}", True, (0,0,0))
  818.         fuel_text = font.render(f"Fuel: {current_tank.fuel:.0f}", True, (0,0,0))
  819.         wind_text = font.render(f"Wind: {wind:.2f}", True, (0,0,0))
  820.         points_text = font.render(f"Points: {current_tank.points}", True, current_tank.color)
  821.         screen.blit(angle_text, (20, 20))
  822.         screen.blit(power_text, (20, 50))
  823.         screen.blit(weapon_text, (20, 80))
  824.         screen.blit(fuel_text, (20, 110))
  825.         screen.blit(wind_text, (20, 140))
  826.         screen.blit(points_text, (20, 170))
  827.         p1_score_text = font.render(f"P1 Score: {player1.score}", True, player1.color)
  828.         p2_score_text = font.render(f"P2 Score: {player2.score}", True, player2.color)
  829.         screen.blit(p1_score_text, (SCREEN_WIDTH - 180, 20))
  830.         screen.blit(p2_score_text, (SCREEN_WIDTH - 180, 50))
  831.     elif game_state == "GAMEOVER" and not menu.active:
  832.         winner_index = -1
  833.         if not player1.is_alive and not player2.is_alive:
  834.             pass
  835.         elif not player2.is_alive:
  836.             winner_index = 0
  837.         elif not player1.is_alive:
  838.             winner_index = 1
  839.         if winner_index == -1:
  840.             win_text_surface = game_over_font.render("MUTUAL DESTRUCTION!", True, (255, 255, 0))
  841.         else:
  842.             winner = players[winner_index]
  843.             win_text_surface = game_over_font.render(f"PLAYER {winner_index+1} WINS!", True, winner.color)
  844.         win_text_rect = win_text_surface.get_rect(center=(SCREEN_WIDTH/2, SCREEN_HEIGHT/2 - 50))
  845.         screen.blit(win_text_surface, win_text_rect)
  846.         reset_text_surface = font.render("Press 'R' to play again or 'M' for Menu", True, (0,0,0))
  847.         reset_text_rect = reset_text_surface.get_rect(center=(SCREEN_WIDTH/2, SCREEN_HEIGHT/2 + 20))
  848.         screen.blit(reset_text_surface, reset_text_rect)
  849.  
  850.     menu.draw(screen, players)
  851.     pygame.display.flip()
  852.     clock.tick(60)
  853.     frame_count += 1
  854.  
  855. pygame.quit()
  856.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement