Guest User

Untitled

a guest
Oct 20th, 2023
333
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.67 KB | Gaming | 0 0
  1. #!/usr/bin/env python
  2. # coding: utf-8
  3.  
  4. import math
  5. import json
  6. from PIL import Image, ImageDraw
  7.  
  8.  
  9. def lerp(a, b, t):
  10.     """Linear interpolation."""
  11.     return a + (b - a) * t
  12.  
  13. def movement_table(orientation='flat'):
  14.     if orientation == 'flat':
  15.         # Clockwise starting from East direction for flat-top hexagon
  16.         directions = [(1, -1, 0), (0, -1, 1), (-1, 0, 1), (-1, 1, 0), (0, 1, -1), (1, 0, -1)]
  17.     elif orientation == 'pointy':
  18.         # Clockwise starting from Northeast direction for pointy-top hexagon
  19.         directions = [(1, 0, -1), (1, -1, 0), (0, -1, 1), (-1, 0, 1), (-1, 1, 0), (0, 1, -1)]
  20.     else:
  21.         raise ValueError('Invalid orientation. Choose either "flat" or "pointy".')
  22.  
  23.     movement_table = {}
  24.     for i, direction in enumerate(directions):
  25.         movement_table[i] = direction
  26.     return movement_table
  27.  
  28.  
  29. class Grid:
  30.     def __init__(self, size):
  31.         self.size = size
  32.         self.grid = self.create_hex_grid()
  33.  
  34.     def create_hex_grid(self):
  35.         """Create a hexagon grid."""
  36.         grid = {}
  37.         size = self.size
  38.  
  39.         for x in range(-size, size + 1):
  40.             for y in range(max(-size, -x - size), min(size, -x + size) + 1):
  41.                 z = -x-y
  42.                 coords = (x, y, z)
  43.                 grid[coords] = {}
  44.         return grid
  45.  
  46.    
  47.     def get_hexagon(self, coords):
  48.         """Retrieve a hexagon from the grid using its coordinates."""
  49.         x, y, z = coords['x'], coords['y'], coords['z']
  50.         return self.grid.get((x, y, z), None)
  51.  
  52.    
  53.     def hexes_in_range(self, center_coords, N, exclude_center=False):
  54.         """Get a list of hexes within a range of a center hex. Can exclude the center hex."""
  55.         results = []
  56.         for dx in range(-N, N+1):
  57.             for dy in range(max(-N, -dx-N), min(N, -dx+N)+1):
  58.                 dz = -dx-dy
  59.                 coords = {'x': center_coords['x'] + dx, 'y': center_coords['y'] + dy, 'z': center_coords['z'] + dz}
  60.                 # If excluding center, skip the center hex
  61.                 if exclude_center and coords == center_coords:
  62.                     continue
  63.                 results.append(coords)
  64.         return results
  65.    
  66.     def neighbors(self, coords):
  67.         """Get the neighbors of a hexagon."""
  68.         return self.hexes_in_range(coords, 1, exclude_center=True)
  69.  
  70.     def get_properties(self, coordinates, prop=None):
  71.         """Get the properties of a hexagon."""
  72.         coords = (coordinates['x'], coordinates['y'], coordinates['z'])
  73.  
  74.         if coords in self.grid:
  75.             hexagon = self.grid[coords]
  76.             if prop:
  77.                 return {k: hexagon.get(k, None) for k in prop}
  78.             else:
  79.                 return hexagon
  80.         else:
  81.             return None
  82.  
  83.  
  84.     def set_properties(self, coordinates, prop):
  85.         """Set the properties of a hexagon."""
  86.         coords = (coordinates['x'], coordinates['y'], coordinates['z'])
  87.        
  88.         if coords in self.grid:
  89.             self.grid[coords].update(prop)
  90.             return True
  91.         else:
  92.             return False
  93.    
  94.     def get_relative_coordinates(self, start_coords, direction, N):
  95.         """Get the coordinates of a hexagon relative to another hexagon."""
  96.         dq, dr, ds = movement_table()[direction]
  97.         new_coords = {
  98.             'x': start_coords['x'] + dq * N,
  99.             'y': start_coords['y'] + dr * N,
  100.             'z': start_coords['z'] + ds * N
  101.         }
  102.         return new_coords
  103.    
  104.     def cube_distance(self, start_coords, end_coords):
  105.         """Get the distance between two hexes in cube coordinates."""
  106.         return max(abs(start_coords['x'] - end_coords['x']), abs(start_coords['y'] - end_coords['y']), abs(start_coords['z'] - end_coords['z']))
  107.    
  108.     def cube_round(self, cube):
  109.         """Round cube coordinates to the nearest hexagon coordinates."""
  110.         rx = round(cube['x'])
  111.         ry = round(cube['y'])
  112.         rz = round(cube['z'])
  113.        
  114.         x_diff = abs(rx - cube['x'])
  115.         y_diff = abs(ry - cube['y'])
  116.         z_diff = abs(rz - cube['z'])
  117.        
  118.         if x_diff > y_diff and x_diff > z_diff:
  119.             rx = -ry-rz
  120.         elif y_diff > z_diff:
  121.             ry = -rx-rz
  122.         else:
  123.             rz = -rx-ry
  124.  
  125.         return {'x': rx, 'y': ry, 'z': rz}
  126.    
  127.     def hexes_in_path(self, start_coords, end_coords):
  128.         """Get a list of hexes in a line between two hexes."""
  129.         N = self.cube_distance(start_coords, end_coords)
  130.         results = []
  131.         for i in range(0, N+1):
  132.             t = 1.0/N * i
  133.             cube = {
  134.                 'x': lerp(start_coords['x'], end_coords['x'], t),
  135.                 'y': lerp(start_coords['y'], end_coords['y'], t),
  136.                 'z': lerp(start_coords['z'], end_coords['z'], t)
  137.             }
  138.             results.append(self.cube_round(cube))
  139.         return results
  140.    
  141.     def hex_range_intersection(self, center_a, range_a, center_b, range_b):
  142.         a_list = self.hexes_in_range(center_a, range_a)
  143.         b_list = self.hexes_in_range(center_b, range_b)
  144.         return [hexagon for hexagon in a_list if hexagon in b_list]
  145.  
  146.     def flood_fill(self, center_coords, N):
  147.         """Get a list of hexes within a range of a center hex that are not obstacles."""
  148.         fringes = []
  149.         for k in range(N+1):
  150.             fringes.append([])
  151.  
  152.         # The start hexagon is at distance 0
  153.         fringes[0].append(center_coords)
  154.  
  155.         for k in range(1, N+1):
  156.             for hex_coords in fringes[k-1]:
  157.                 # Check the neighbors of this hexagon
  158.                 for direction in range(6):
  159.                     # Calculate the coordinates of the neighboring hex
  160.                     neighbor_coords = self.get_relative_coordinates(hex_coords, direction, 1)
  161.  
  162.                     # Check if this hex is already in a fringe
  163.                     if any(neighbor_coords in fringe for fringe in fringes):
  164.                         continue
  165.                    
  166.                     # Check if this hex is an obstacle
  167.                     hex_properties = self.get_properties(neighbor_coords)
  168.                     if hex_properties.get('obstacle', False):
  169.                         continue
  170.  
  171.                     # This hex is not an obstacle and not yet in a fringe, so we add it to the current fringe
  172.                     fringes[k].append(neighbor_coords)
  173.  
  174.         return fringes
  175.    
  176.     def update_properties_from_list(self, hex_list):
  177.         """Updates the properties of each hex in the grid from a list of hexagon objects."""
  178.         for hex_obj in hex_list:
  179.             # Ensure the object has the required keys
  180.             if 'coords' in hex_obj and 'props' in hex_obj:
  181.                 coords = tuple(hex_obj['coords'].values())  # Convert coordinates to a tuple
  182.                 self.set_properties(coords, hex_obj['props'])
  183.             else:
  184.                 raise ValueError("Hexagon object must contain 'coords' and 'props' keys.")
  185.         return self.grid
  186.  
  187.    
  188.     def direction_to_index(self, direction_str, orientation='flat'):
  189.         print(direction_str, orientation)
  190.         """Convert a string direction to its corresponding index in the movement table."""
  191.         # Define mapping for flat orientation
  192.         if orientation == 'flat':
  193.             direction_mapping = {
  194.                 'NE': 0,
  195.                 'SE': 1,
  196.                 'S': 2,
  197.                 'SW': 3,
  198.                 'NW': 4,
  199.                 'N': 5
  200.             }
  201.         elif orientation == 'pointy':
  202.             direction_mapping = {
  203.                 'NE': 0,
  204.                 'E': 1,
  205.                 'SE': 2,
  206.                 'SW': 3,
  207.                 'W': 4,
  208.                 'NW': 5
  209.             }
  210.         else:
  211.             raise ValueError('Invalid orientation. Choose either "flat" or "pointy".')
  212.        
  213.         if direction_str not in direction_mapping:
  214.             raise ValueError(f"Invalid direction. Choose from {list(direction_mapping.keys())}.")
  215.        
  216.         return direction_mapping[direction_str]
  217.  
  218.     def index_to_direction(self, direction_index, orientation='flat'):
  219.         """Convert an index in the movement table to its corresponding string direction."""
  220.         # Define mapping for flat orientation
  221.         if orientation == 'flat':
  222.             index_mapping = {
  223.                 0: 'NE',
  224.                 1: 'SE',
  225.                 2: 'S',
  226.                 3: 'SW',
  227.                 4: 'NW',
  228.                 5: 'N'
  229.             }
  230.         elif orientation == 'pointy':
  231.             index_mapping = {
  232.                 0: 'NE',
  233.                 1: 'E',
  234.                 2: 'SE',
  235.                 3: 'SW',
  236.                 4: 'W',
  237.                 5: 'NW'
  238.             }
  239.         else:
  240.             raise ValueError('Invalid orientation. Choose either "flat" or "pointy".')
  241.  
  242.         if direction_index not in index_mapping:
  243.             raise ValueError(f"Invalid index. Choose from {list(index_mapping.keys())}.")
  244.  
  245.         return index_mapping[direction_index]
  246.    
  247.     def flat_hex_corner(self, center, size, i):
  248.         angle_deg = 60 * i
  249.         angle_rad = math.pi / 180 * angle_deg
  250.         return {'x': center['x'] + size * math.cos(angle_rad),
  251.                 'y': center['y'] + size * math.sin(angle_rad)}
  252.  
  253.     def hex_to_pixel(self, coords, size, grid_size, orientation='flat'):
  254.         """Convert a hexagon's cube coordinates to pixel coordinates."""
  255.         x, y, z = coords  # Unpack tuple
  256.         if orientation == 'flat':
  257.             px = size * (3/2 * x)
  258.             py = size * (math.sqrt(3) * (y + x / 2))
  259.         elif orientation == 'pointy':
  260.             px = size * (math.sqrt(3) * (x + y / 2))
  261.             py = size * (3/2 * y)
  262.         else:
  263.             raise ValueError('Invalid orientation. Choose either "flat" or "pointy".')
  264.  
  265.         # Adjust coordinates for image center
  266.         center_x = size * grid_size
  267.         center_y = size * grid_size
  268.  
  269.         return {'x': px + center_x, 'y': py + center_y}
  270.  
  271.  
  272.  
  273.     def draw_grid(self, size, output_file="hexagons.png"):
  274.         """Draw the hexagonal grid and save it to a file."""
  275.         print('drawing grid')
  276.  
  277.         # Calculate the minimum and maximum x, y coordinates in pixels
  278.         min_x = min_y = float('inf')
  279.         max_x = max_y = float('-inf')
  280.  
  281.         for coords in self.grid.keys():
  282.             pixel_coords = self.hex_to_pixel(coords, size, len(self.grid), "flat")
  283.             min_x = min(min_x, pixel_coords['x'])
  284.             min_y = min(min_y, pixel_coords['y'])
  285.             max_x = max(max_x, pixel_coords['x'])
  286.             max_y = max(max_y, pixel_coords['y'])
  287.  
  288.         # Create a margin around the grid for aesthetics
  289.         margin = size
  290.  
  291.         # Set the image size based on the minimum and maximum coordinates
  292.         img_size = (int(max_x - min_x + 2*margin), int(max_y - min_y + 2*margin))
  293.         img = Image.new('RGB', img_size)
  294.  
  295.         draw = ImageDraw.Draw(img)
  296.  
  297.         for coords in self.grid.keys():
  298.             pixel_coords = self.hex_to_pixel(coords, size, len(self.grid), "flat")
  299.  
  300.             # Adjust for the new image origin and margin
  301.             pixel_coords['x'] -= min_x - margin
  302.             pixel_coords['y'] -= min_y - margin
  303.  
  304.             corners = [self.flat_hex_corner(pixel_coords, size, i) for i in range(6)]
  305.             corners = [(corner['x'], corner['y']) for corner in corners]
  306.             draw.polygon(corners, outline='white')
  307.  
  308.         img.save(output_file)
  309.  
  310.     def to_json(self):
  311.         json_safe_dict = {str(k): v for k, v in self.grid.items()}
  312.         return json.dumps(json_safe_dict)
  313.  
  314.     def from_json(self, json_str):
  315.         json_safe_dict = json.loads(json_str)
  316.         self.grid = {tuple(map(int, k.strip('()').split(','))): v for k, v in json_safe_dict.items()}
Advertisement
Add Comment
Please, Sign In to add comment