Advertisement
nitestryker

tower_def_game.py

Apr 14th, 2025 (edited)
506
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 21.24 KB | None | 0 0
  1. # A Primitive Example of a tower defense game similar to clash royale written in pygame
  2. # click on the grid to place objects that will find the nearest enemy tower
  3.  
  4. import pygame
  5. import sys
  6. import os
  7. import math
  8. import random
  9. import heapq  # For A* pathfinding
  10.  
  11. # Initialize pygame
  12. pygame.init()
  13.  
  14. # Constants
  15. GRID_SIZE = 18  # Increased from 15 to 18 for more play area
  16. CELL_SIZE = 35  # Slightly smaller cells to fit on screen
  17. MARGIN = 4
  18. WINDOW_SIZE = (GRID_SIZE * (CELL_SIZE + MARGIN) + MARGIN,
  19.                GRID_SIZE * (CELL_SIZE + MARGIN) + MARGIN)
  20.  
  21. # Colors
  22. BLACK = (0, 0, 0)
  23. WHITE = (255, 255, 255)
  24. GRAY = (200, 200, 200)
  25. GREEN = (0, 255, 0)
  26. BLUE = (100, 100, 255)
  27. RED = (255, 100, 100)
  28. YELLOW = (255, 255, 100)
  29. PURPLE = (200, 100, 255)
  30. RIVER_BLUE = (80, 170, 255)
  31. LIGHT_GRASS = (140, 200, 100)
  32. DARK_GRASS = (120, 180, 80)
  33. BRIDGE_BROWN = (150, 100, 50)
  34.  
  35. # Create the grid
  36. grid = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
  37.  
  38. # Create river position (horizontal line in middle of the grid)
  39. river_row_start = 8  # Adjusted for larger grid
  40. river_row_end = 10   # Making river slightly wider (2 rows)
  41.  
  42. # Define bridge positions (aligned with princess towers)
  43. left_bridge_col = 3  # Left bridge aligned with left princess towers
  44. right_bridge_col = 13  # Right bridge adjusted for wider grid
  45. bridge_width = 2  # Width of each bridge
  46.  
  47. # Define tower placement areas - TOP (Player 1 side)
  48. # Main tower (Top): Centered in the grid, 3x3
  49. tower1_top_x, tower1_top_y = 8, 1  # King tower position
  50. tower1_top_size = 3
  51.  
  52. # Princess towers (Top): On each side of the main tower, one row down from king tower
  53. tower2_top_x, tower2_top_y = 3, 2  # Left princess tower (one row down from king tower)
  54. tower2_top_size = 2
  55. tower3_top_x, tower3_top_y = 13, 2  # Right princess tower (one row down from king tower)
  56. tower3_top_size = 2
  57.  
  58. # Define tower placement areas - BOTTOM (Player 2 side)
  59. # Main tower (Bottom): Centered in the grid, 3x3
  60. tower1_bottom_x, tower1_bottom_y = 8, 14  # King tower position
  61. tower1_bottom_size = 3
  62.  
  63. # Princess towers (Bottom): On each side of the main tower, aligned with king tower top
  64. tower2_bottom_x, tower2_bottom_y = 3, 14  # Left princess tower (aligned with king tower top)
  65. tower2_bottom_size = 2
  66. tower3_bottom_x, tower3_bottom_y = 13, 14  # Right princess tower (aligned with king tower top)
  67. tower3_bottom_size = 2
  68.  
  69. # Create separate lists to store different tower positions
  70. king_tower_positions = []
  71. princess_tower_positions = []
  72. blue_tower_positions = []  # To track our own towers for pathfinding
  73.  
  74. # Add king tower positions (red)
  75. for row in range(tower1_top_y, tower1_top_y + tower1_top_size):
  76.     for col in range(tower1_top_x, tower1_top_x + tower1_top_size):
  77.         king_tower_positions.append((row, col))
  78.  
  79. # Add princess tower positions (red)
  80. for row in range(tower2_top_y, tower2_top_y + tower2_top_size):
  81.     for col in range(tower2_top_x, tower2_top_x + tower2_top_size):
  82.         princess_tower_positions.append((row, col))
  83.  
  84. for row in range(tower3_top_y, tower3_top_y + tower3_top_size):
  85.     for col in range(tower3_top_x, tower3_top_x + tower3_top_size):
  86.         princess_tower_positions.append((row, col))
  87.  
  88. # Add blue tower positions (used to prevent placement and for pathfinding)
  89. # King tower (blue)
  90. for row in range(tower1_bottom_y, tower1_bottom_y + tower1_bottom_size):
  91.     for col in range(tower1_bottom_x, tower1_bottom_x + tower1_bottom_size):
  92.         blue_tower_positions.append((row, col))
  93.  
  94. # Princess towers (blue)
  95. for row in range(tower2_bottom_y, tower2_bottom_y + tower2_bottom_size):
  96.     for col in range(tower2_bottom_x, tower2_bottom_x + tower2_bottom_size):
  97.         blue_tower_positions.append((row, col))
  98.  
  99. for row in range(tower3_bottom_y, tower3_bottom_y + tower3_bottom_size):
  100.     for col in range(tower3_bottom_x, tower3_bottom_x + tower3_bottom_size):
  101.         blue_tower_positions.append((row, col))
  102.        
  103. # Combine all red tower positions (will be used for display purposes)
  104. red_tower_positions = king_tower_positions + princess_tower_positions
  105.  
  106. # Troop class to handle movement and rendering
  107. class Troop:
  108.     def __init__(self, row, col, color=(0, 0, 255)):
  109.         self.row = row
  110.         self.col = col
  111.         # Store position as floats for smooth movement
  112.         self.exact_x = col
  113.         self.exact_y = row
  114.         self.color = color
  115.         self.path = []
  116.         self.speed = 1.0  # Cells per second
  117.         self.size = CELL_SIZE - 10
  118.         self.active = True
  119.        
  120.         # Find path to nearest red tower
  121.         self.find_path()
  122.    
  123.     def find_path(self):
  124.         """Find path to nearest red tower using A* pathfinding"""
  125.         # First check if princess towers are still available
  126.         if princess_tower_positions:
  127.             # Find closest princess tower
  128.             closest_princess = min(princess_tower_positions,
  129.                                   key=lambda t: math.sqrt((self.row - t[0])**2 + (self.col - t[1])**2))
  130.             princess_dist = math.sqrt((self.row - closest_princess[0])**2 +
  131.                                      (self.col - closest_princess[1])**2)
  132.            
  133.             # Find closest king tower
  134.             closest_king = min(king_tower_positions,
  135.                               key=lambda t: math.sqrt((self.row - t[0])**2 + (self.col - t[1])**2))
  136.             king_dist = math.sqrt((self.row - closest_king[0])**2 +
  137.                                  (self.col - closest_king[1])**2)
  138.            
  139.             # Choose the closer of the two
  140.             if princess_dist <= king_dist + 2:  # Slight preference for princess towers
  141.                 target = closest_princess
  142.                 print("Targeting princess tower at", target)
  143.             else:
  144.                 target = closest_king
  145.                 print("Targeting king tower at", target)
  146.         else:
  147.             # All princess towers destroyed, target king tower
  148.             target = min(king_tower_positions,
  149.                         key=lambda t: math.sqrt((self.row - t[0])**2 + (self.col - t[1])**2))
  150.             print("No princess towers left, targeting king tower at", target)
  151.        
  152.         # A* pathfinding - include blue towers as obstacles to path around
  153.         self.path = astar_pathfinding((self.row, self.col), target, include_blue_towers=True)
  154.        
  155.         if not self.path:
  156.             print("No path found!")
  157.             self.active = False
  158.         else:
  159.             # Remove the starting position from the path
  160.             if self.path and len(self.path) > 0:
  161.                 self.path.pop(0)
  162.    
  163.     def update(self, dt):
  164.         """Update troop position"""
  165.         if not self.active or not self.path:
  166.             return
  167.        
  168.         # Get the next position to move toward
  169.         next_pos = self.path[0]
  170.        
  171.         # Calculate direction
  172.         dx = next_pos[1] - self.exact_x
  173.         dy = next_pos[0] - self.exact_y
  174.        
  175.         # Normalize direction if not 0
  176.         distance = math.sqrt(dx**2 + dy**2)
  177.         if distance > 0:
  178.             dx /= distance
  179.             dy /= distance
  180.        
  181.         # Move towards the next position
  182.         move_distance = self.speed * dt
  183.         if distance <= move_distance:
  184.             # We've reached the next position
  185.             self.exact_x = next_pos[1]
  186.             self.exact_y = next_pos[0]
  187.             self.path.pop(0)
  188.            
  189.             # Check if we've reached the target
  190.             if not self.path:
  191.                 print("Reached target!")
  192.                 self.active = False
  193.         else:
  194.             # Move towards the next position
  195.             self.exact_x += dx * move_distance
  196.             self.exact_y += dy * move_distance
  197.        
  198.         # Update integer position
  199.         self.row = int(self.exact_y)
  200.         self.col = int(self.exact_x)
  201.    
  202.     def draw(self, screen):
  203.         """Draw the troop on the screen"""
  204.         if not self.active:
  205.             return
  206.            
  207.         # Calculate screen position
  208.         x = self.exact_x * (CELL_SIZE + MARGIN) + MARGIN + CELL_SIZE // 2
  209.         y = self.exact_y * (CELL_SIZE + MARGIN) + MARGIN + CELL_SIZE // 2
  210.        
  211.         # Draw the troop
  212.         pygame.draw.circle(screen, self.color, (int(x), int(y)), self.size // 2)
  213.        
  214.         # Add a border
  215.         pygame.draw.circle(screen, (0, 0, 200), (int(x), int(y)), self.size // 2, 2)
  216.  
  217. # Pathfinding functions
  218. def is_valid_cell(row, col, include_blue_towers=False):
  219.     """Check if a cell is valid for pathfinding"""
  220.     # Check if within grid bounds
  221.     if not (0 <= row < GRID_SIZE and 0 <= col < GRID_SIZE):
  222.         return False
  223.    
  224.     # Check if it's a river (not a bridge)
  225.     if river_row_start <= row < river_row_end:
  226.         is_bridge = ((left_bridge_col <= col < left_bridge_col + bridge_width) or
  227.                    (right_bridge_col <= col < right_bridge_col + bridge_width))
  228.         if not is_bridge:
  229.             return False
  230.    
  231.     # Check if it's a blue tower position (only for pathfinding, not for placement)
  232.     if include_blue_towers and (row, col) in blue_tower_positions:
  233.         return False
  234.    
  235.     return True
  236.  
  237. def get_neighbors(node, include_blue_towers=False):
  238.     """Get valid neighboring cells for pathfinding"""
  239.     row, col = node
  240.     neighbors = []
  241.    
  242.     # Check all 4 adjacent cells
  243.     directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
  244.     for dr, dc in directions:
  245.         new_row, new_col = row + dr, col + dc
  246.         if is_valid_cell(new_row, new_col, include_blue_towers):
  247.             neighbors.append((new_row, new_col))
  248.    
  249.     return neighbors
  250.  
  251. def heuristic(a, b):
  252.     """Calculate the manhattan distance heuristic"""
  253.     return abs(a[0] - b[0]) + abs(a[1] - b[1])
  254.  
  255. def astar_pathfinding(start, goal, include_blue_towers=False):
  256.     """A* pathfinding algorithm to find a path from start to goal"""
  257.     frontier = []
  258.     heapq.heappush(frontier, (0, start))
  259.     came_from = {start: None}
  260.     cost_so_far = {start: 0}
  261.    
  262.     while frontier:
  263.         _, current = heapq.heappop(frontier)
  264.        
  265.         if current == goal:
  266.             break
  267.        
  268.         for next_node in get_neighbors(current, include_blue_towers):
  269.             new_cost = cost_so_far[current] + 1
  270.             if next_node not in cost_so_far or new_cost < cost_so_far[next_node]:
  271.                 cost_so_far[next_node] = new_cost
  272.                 priority = new_cost + heuristic(next_node, goal)
  273.                 heapq.heappush(frontier, (priority, next_node))
  274.                 came_from[next_node] = current
  275.    
  276.     # Reconstruct the path
  277.     if goal not in came_from:
  278.         return []
  279.        
  280.     path = [goal]
  281.     while path[-1] != start:
  282.         path.append(came_from[path[-1]])
  283.    
  284.     path.reverse()
  285.     return path
  286.  
  287. # List to store troops
  288. troops = []
  289.  
  290. # Set up the display
  291. screen = pygame.display.set_mode(WINDOW_SIZE)
  292. pygame.display.set_caption("Clash Royale Style Grid")
  293. clock = pygame.time.Clock()
  294.  
  295. # Animation variables
  296. time_passed = 0
  297.  
  298. # Main game loop
  299. running = True
  300. while running:
  301.     # Get delta time for smooth animations
  302.     dt = clock.get_time() / 1000.0  # Time in seconds
  303.    
  304.     for event in pygame.event.get():
  305.         if event.type == pygame.QUIT:
  306.             running = False
  307.         elif event.type == pygame.MOUSEBUTTONDOWN:
  308.             # Get position of the mouse click
  309.             pos = pygame.mouse.get_pos()
  310.             # Calculate the row and column clicked
  311.             column = pos[0] // (CELL_SIZE + MARGIN)
  312.             row = pos[1] // (CELL_SIZE + MARGIN)
  313.            
  314.             # Make sure it's within the grid
  315.             if 0 <= row < GRID_SIZE and 0 <= column < GRID_SIZE:
  316.                 # Check if it's not a river (except bridges)
  317.                 valid_position = True
  318.                 if river_row_start <= row < river_row_end:
  319.                     is_bridge = ((left_bridge_col <= column < left_bridge_col + bridge_width) or
  320.                                (right_bridge_col <= column < right_bridge_col + bridge_width))
  321.                     if not is_bridge:
  322.                         valid_position = False
  323.                
  324.                 # Don't place troops on the top (enemy) side
  325.                 if row < river_row_start:
  326.                     valid_position = False
  327.                
  328.                 # Don't place troops on blue towers
  329.                 if (row, column) in blue_tower_positions:
  330.                     valid_position = False
  331.                
  332.                 if valid_position:
  333.                     # Create a new troop
  334.                     new_troop = Troop(row, column)
  335.                     troops.append(new_troop)
  336.                     print(f"Placed a troop at ({row}, {column})")
  337.                 else:
  338.                     print("Invalid placement position")
  339.    
  340.     # Update troops
  341.     for troop in troops:
  342.         troop.update(dt)
  343.    
  344.     # Remove inactive troops
  345.     troops = [troop for troop in troops if troop.active]
  346.    
  347.     # Update animation time
  348.     time_passed += dt
  349.    
  350.     # Clear screen
  351.     screen.fill(BLACK)
  352.    
  353.     # Draw background terrain (alternating grass pattern)
  354.     for row in range(GRID_SIZE):
  355.         for column in range(GRID_SIZE):
  356.             # Calculate the position for each cell
  357.             x = column * (CELL_SIZE + MARGIN) + MARGIN
  358.             y = row * (CELL_SIZE + MARGIN) + MARGIN
  359.            
  360.             # Alternate between light and dark grass for a checkerboard effect
  361.             if (row + column) % 2 == 0:
  362.                 grass_color = LIGHT_GRASS
  363.             else:
  364.                 grass_color = DARK_GRASS
  365.                
  366.             # Draw the terrain cell
  367.             pygame.draw.rect(screen, grass_color, [x, y, CELL_SIZE, CELL_SIZE])
  368.    
  369.     # Draw the river crossing the middle
  370.     for row in range(river_row_start, river_row_end):
  371.         for column in range(GRID_SIZE):
  372.             x = column * (CELL_SIZE + MARGIN) + MARGIN
  373.             y = row * (CELL_SIZE + MARGIN) + MARGIN
  374.            
  375.             # Check if this is a bridge position
  376.             is_bridge = ((left_bridge_col <= column < left_bridge_col + bridge_width) or
  377.                         (right_bridge_col <= column < right_bridge_col + bridge_width))
  378.            
  379.             if is_bridge:
  380.                 # Draw bridge
  381.                 pygame.draw.rect(screen, BRIDGE_BROWN, [x, y, CELL_SIZE, CELL_SIZE])
  382.                 # Add bridge planks
  383.                 for i in range(3):
  384.                     plank_y = y + (i+1) * CELL_SIZE // 4
  385.                     pygame.draw.line(screen, (100, 70, 30),
  386.                                     (x, plank_y), (x + CELL_SIZE, plank_y), 2)
  387.             else:
  388.                 # Draw river
  389.                 pygame.draw.rect(screen, RIVER_BLUE, [x, y, CELL_SIZE, CELL_SIZE])
  390.                
  391.                 # Add wave animation to the river
  392.                 wave_height = 3 * math.sin(column / 2 + time_passed * 2)
  393.                 pygame.draw.line(screen, (150, 220, 255),
  394.                                 (x, y + CELL_SIZE // 2 + wave_height),
  395.                                 (x + CELL_SIZE, y + CELL_SIZE // 2 - wave_height), 2)
  396.    
  397.     # Add some bubbles/foam to the river (avoid bridges)
  398.     for i in range(5):
  399.         bubble_x = (int(time_passed * 100 + i * 100) % WINDOW_SIZE[0])
  400.         bubble_y = river_row_start * (CELL_SIZE + MARGIN) + MARGIN + CELL_SIZE // 2
  401.        
  402.         # Check if bubble is on a bridge
  403.         bubble_col = bubble_x // (CELL_SIZE + MARGIN)
  404.         is_on_bridge = ((left_bridge_col <= bubble_col < left_bridge_col + bridge_width) or
  405.                         (right_bridge_col <= bubble_col < right_bridge_col + bridge_width))
  406.        
  407.         if not is_on_bridge:
  408.             bubble_size = random.randint(2, 4)
  409.             pygame.draw.circle(screen, (200, 230, 255), (bubble_x, bubble_y), bubble_size)
  410.    
  411.     # Draw the tower areas and grid cells
  412.     for row in range(GRID_SIZE):
  413.         for column in range(GRID_SIZE):
  414.             # Skip if this is a river cell (not a bridge)
  415.             is_bridge = False
  416.             if river_row_start <= row < river_row_end:
  417.                 is_bridge = ((left_bridge_col <= column < left_bridge_col + bridge_width) or
  418.                            (right_bridge_col <= column < right_bridge_col + bridge_width))
  419.                 if not is_bridge:
  420.                     continue
  421.                
  422.             # Calculate the position for each cell
  423.             x = column * (CELL_SIZE + MARGIN) + MARGIN
  424.             y = row * (CELL_SIZE + MARGIN) + MARGIN
  425.            
  426.             # Determine if this cell is in a tower area
  427.             is_tower_cell = False
  428.            
  429.             # Check top towers (red team)
  430.             if (tower1_top_x <= column < tower1_top_x + tower1_top_size and
  431.                 tower1_top_y <= row < tower1_top_y + tower1_top_size):
  432.                 # Main tower (top) - King tower
  433.                 color = RED
  434.                 is_tower_cell = True
  435.                 if grid[row][column] == 1:
  436.                     # Draw activated tower
  437.                     pygame.draw.rect(screen, (255, 50, 50), [x, y, CELL_SIZE, CELL_SIZE])
  438.                 else:
  439.                     # Draw tower with border
  440.                     pygame.draw.rect(screen, RED, [x, y, CELL_SIZE, CELL_SIZE])
  441.                     pygame.draw.rect(screen, (255, 0, 0), [x+2, y+2, CELL_SIZE-4, CELL_SIZE-4], 2)
  442.             elif (tower2_top_x <= column < tower2_top_x + tower2_top_size and
  443.                   tower2_top_y <= row < tower2_top_y + tower2_top_size):
  444.                 # Princess tower 1 (top)
  445.                 color = RED
  446.                 is_tower_cell = True
  447.                 if grid[row][column] == 1:
  448.                     # Draw activated tower
  449.                     pygame.draw.rect(screen, (255, 50, 50), [x, y, CELL_SIZE, CELL_SIZE])
  450.                 else:
  451.                     # Draw tower with border
  452.                     pygame.draw.rect(screen, RED, [x, y, CELL_SIZE, CELL_SIZE])
  453.                     pygame.draw.rect(screen, (255, 0, 0), [x+2, y+2, CELL_SIZE-4, CELL_SIZE-4], 2)
  454.             elif (tower3_top_x <= column < tower3_top_x + tower3_top_size and
  455.                   tower3_top_y <= row < tower3_top_y + tower3_top_size):
  456.                 # Princess tower 2 (top)
  457.                 color = RED
  458.                 is_tower_cell = True
  459.                 if grid[row][column] == 1:
  460.                     # Draw activated tower
  461.                     pygame.draw.rect(screen, (255, 50, 50), [x, y, CELL_SIZE, CELL_SIZE])
  462.                 else:
  463.                     # Draw tower with border
  464.                     pygame.draw.rect(screen, RED, [x, y, CELL_SIZE, CELL_SIZE])
  465.                     pygame.draw.rect(screen, (255, 0, 0), [x+2, y+2, CELL_SIZE-4, CELL_SIZE-4], 2)
  466.            
  467.             # Check bottom towers (blue team)
  468.             elif (tower1_bottom_x <= column < tower1_bottom_x + tower1_bottom_size and
  469.                   tower1_bottom_y <= row < tower1_bottom_y + tower1_bottom_size):
  470.                 # Main tower (bottom) - King tower
  471.                 color = BLUE
  472.                 is_tower_cell = True
  473.                 if grid[row][column] == 1:
  474.                     # Draw activated tower
  475.                     pygame.draw.rect(screen, (50, 50, 255), [x, y, CELL_SIZE, CELL_SIZE])
  476.                 else:
  477.                     # Draw tower with border
  478.                     pygame.draw.rect(screen, BLUE, [x, y, CELL_SIZE, CELL_SIZE])
  479.                     pygame.draw.rect(screen, (0, 0, 255), [x+2, y+2, CELL_SIZE-4, CELL_SIZE-4], 2)
  480.             elif (tower2_bottom_x <= column < tower2_bottom_x + tower2_bottom_size and
  481.                   tower2_bottom_y <= row < tower2_bottom_y + tower2_bottom_size):
  482.                 # Princess tower 1 (bottom)
  483.                 color = BLUE
  484.                 is_tower_cell = True
  485.                 if grid[row][column] == 1:
  486.                     # Draw activated tower
  487.                     pygame.draw.rect(screen, (50, 50, 255), [x, y, CELL_SIZE, CELL_SIZE])
  488.                 else:
  489.                     # Draw tower with border
  490.                     pygame.draw.rect(screen, BLUE, [x, y, CELL_SIZE, CELL_SIZE])
  491.                     pygame.draw.rect(screen, (0, 0, 255), [x+2, y+2, CELL_SIZE-4, CELL_SIZE-4], 2)
  492.             elif (tower3_bottom_x <= column < tower3_bottom_x + tower3_bottom_size and
  493.                   tower3_bottom_y <= row < tower3_bottom_y + tower3_bottom_size):
  494.                 # Princess tower 2 (bottom)
  495.                 color = BLUE
  496.                 is_tower_cell = True
  497.                 if grid[row][column] == 1:
  498.                     # Draw activated tower
  499.                     pygame.draw.rect(screen, (50, 50, 255), [x, y, CELL_SIZE, CELL_SIZE])
  500.                 else:
  501.                     # Draw tower with border
  502.                     pygame.draw.rect(screen, BLUE, [x, y, CELL_SIZE, CELL_SIZE])
  503.                     pygame.draw.rect(screen, (0, 0, 255), [x+2, y+2, CELL_SIZE-4, CELL_SIZE-4], 2)
  504.            
  505.             # Draw normal grid cells (if not a tower)
  506.             elif grid[row][column] == 1:
  507.                 # Draw an activated regular cell (troop placement indicator)
  508.                 pygame.draw.rect(screen, GREEN, [x, y, CELL_SIZE, CELL_SIZE], 2)
  509.    
  510.     # Draw all troops
  511.     for troop in troops:
  512.         troop.draw(screen)
  513.    
  514.     # Update the display
  515.     pygame.display.flip()
  516.    
  517.     # Limit to 60 frames per second
  518.     clock.tick(60)
  519.  
  520. # Clean up
  521. pygame.quit()
  522. sys.exit()
  523.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement