Advertisement
Guest User

wormhole_game_of_life_logic

a guest
Apr 26th, 2025
281
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.23 KB | None | 0 0
  1. import os
  2. import numpy as np
  3. from PIL import Image
  4. import sys
  5.  
  6. class WormholeGameOfLife:
  7. def __init__(self, input_dir):
  8. self.input_dir = input_dir
  9.  
  10. # Load input images
  11. self.starting_position = self.load_binary_image(os.path.join(input_dir, 'starting_position.png'))
  12. self.horizontal_tunnel = self.load_image(os.path.join(input_dir, 'horizontal_tunnel.png'))
  13. self.vertical_tunnel = self.load_image(os.path.join(input_dir, 'vertical_tunnel.png'))
  14.  
  15. # Check all images have the same dimensions
  16. height, width = self.starting_position.shape
  17. assert self.horizontal_tunnel.shape[:2] == (height, width), "Image dimensions don't match"
  18. assert self.vertical_tunnel.shape[:2] == (height, width), "Image dimensions don't match"
  19.  
  20. self.height = height
  21. self.width = width
  22.  
  23. # Find wormhole pairs
  24. self.h_wormholes = self.find_wormhole_pairs(self.horizontal_tunnel)
  25. self.v_wormholes = self.find_wormhole_pairs(self.vertical_tunnel)
  26.  
  27. print(f"Horizontal wormhole pairs: {len(self.h_wormholes) // 2}")
  28. print(f"Vertical wormhole pairs: {len(self.v_wormholes) // 2}")
  29.  
  30. # Create a set of all wormhole positions for quick lookups
  31. self.wormhole_positions = set(self.h_wormholes.keys()) | set(self.v_wormholes.keys())
  32.  
  33. # Current state of the grid
  34. self.grid = self.starting_position.copy()
  35.  
  36. # Load expected results if available
  37. try:
  38. self.expected_1 = self.load_binary_image(os.path.join(input_dir, 'expected-1.png'))
  39. print(f"Expected alive cells after 1 iteration: {np.sum(self.expected_1)}")
  40. except:
  41. self.expected_1 = None
  42.  
  43. def load_binary_image(self, filepath):
  44. """Load a binary image (white=alive, black=dead)"""
  45. img = Image.open(filepath).convert('L')
  46. binary = np.array(img) > 128 # Convert to binary using threshold
  47. return binary
  48.  
  49. def load_image(self, filepath):
  50. """Load a color image"""
  51. img = Image.open(filepath)
  52. return np.array(img)
  53.  
  54. def find_wormhole_pairs(self, tunnel_img):
  55. """Find all wormhole pairs by color"""
  56. wormhole_map = {} # (y, x) -> (y', x')
  57. color_to_pos = {}
  58.  
  59. # Collect all positions for each color
  60. for y in range(self.height):
  61. for x in range(self.width):
  62. color = tuple(tunnel_img[y, x][:3])
  63. if color != (0, 0, 0): # Not black
  64. if color not in color_to_pos:
  65. color_to_pos[color] = []
  66. color_to_pos[color].append((y, x))
  67.  
  68. # Create wormhole mappings between pairs
  69. for color, positions in color_to_pos.items():
  70. if len(positions) == 2:
  71. pos1, pos2 = positions
  72. wormhole_map[pos1] = pos2
  73. wormhole_map[pos2] = pos1
  74.  
  75. return wormhole_map
  76.  
  77. def get_neighbors(self, y, x):
  78. """
  79. Get the 8 neighbors of a cell, considering wormhole tunnels
  80. Wormholes connect two points, allowing neighbors to be counted through the wormhole
  81. """
  82. neighbors = []
  83.  
  84. # Check each of the 8 adjacent cells
  85. for dy in [-1, 0, 1]:
  86. for dx in [-1, 0, 1]:
  87. if dy == 0 and dx == 0:
  88. continue # Skip the cell itself
  89.  
  90. ny, nx = y + dy, x + dx
  91.  
  92. # Check for out of bounds
  93. if ny < 0 or ny >= self.height or nx < 0 or nx >= self.width:
  94. continue
  95.  
  96. # For diagonal neighbors, just add them directly
  97. if dy != 0 and dx != 0:
  98. neighbors.append((ny, nx))
  99. continue
  100.  
  101. # For orthogonal neighbors, check if they're at a wormhole entrance
  102. # If so, count both the regular neighbor AND the neighbor through the wormhole
  103.  
  104. # Vertical wormhole (top or bottom neighbor)
  105. if dx == 0 and (ny, nx) in self.v_wormholes:
  106. # Get the corresponding position at the other end of the wormhole
  107. dest_y, dest_x = self.v_wormholes[(ny, nx)]
  108. # Add both the regular neighbor and the wormhole destination
  109. neighbors.append((ny, nx))
  110. neighbors.append((dest_y, dest_x))
  111.  
  112. # Horizontal wormhole (left or right neighbor)
  113. elif dy == 0 and (ny, nx) in self.h_wormholes:
  114. # Get the corresponding position at the other end of the wormhole
  115. dest_y, dest_x = self.h_wormholes[(ny, nx)]
  116. # Add both the regular neighbor and the wormhole destination
  117. neighbors.append((ny, nx))
  118. neighbors.append((dest_y, dest_x))
  119.  
  120. # Regular neighbor (not at a wormhole)
  121. else:
  122. neighbors.append((ny, nx))
  123.  
  124. return neighbors
  125.  
  126. def count_live_neighbors(self, y, x):
  127. """Count the number of live neighbors, with wormhole connections"""
  128. count = 0
  129. for ny, nx in self.get_neighbors(y, x):
  130. if self.grid[ny, nx]:
  131. count += 1
  132. return count
  133.  
  134. def step(self):
  135. """Advance the simulation by one step"""
  136. new_grid = np.zeros_like(self.grid)
  137.  
  138. # Process each cell in the grid
  139. for y in range(self.height):
  140. for x in range(self.width):
  141. # Special cases for wormhole positions
  142. if (y, x) in self.wormhole_positions:
  143. # Apply special rule: wormhole positions stay alive with any neighbors
  144. if self.grid[y, x] and self.count_live_neighbors(y, x) > 0:
  145. new_grid[y, x] = True
  146. # Otherwise standard Game of Life rules
  147. else:
  148. # Apply standard rules (for birth only)
  149. if not self.grid[y, x] and self.count_live_neighbors(y, x) == 3:
  150. new_grid[y, x] = True
  151. else:
  152. # For non-wormhole positions, apply standard Game of Life rules
  153. live_neighbors = self.count_live_neighbors(y, x)
  154.  
  155. if self.grid[y, x]: # Cell is alive
  156. # A live cell with 2 or 3 live neighbors survives
  157. if live_neighbors in [2, 3]:
  158. new_grid[y, x] = True
  159. else: # Cell is dead
  160. # A dead cell with exactly 3 live neighbors becomes alive
  161. if live_neighbors == 3:
  162. new_grid[y, x] = True
  163.  
  164. # Update the grid state
  165. self.grid = new_grid
  166.  
  167. def simulate(self, save_at_iterations, output_dir):
  168. """Run continuously up to max iteration, saving and verifying at specific steps."""
  169. max_iter = max(save_at_iterations)
  170.  
  171. # Preload expected outputs if available
  172. expected_outputs = {}
  173. for iter_num in save_at_iterations:
  174. expected_path = os.path.join(self.input_dir, f'expected-{iter_num}.png')
  175. if os.path.exists(expected_path):
  176. expected_outputs[iter_num] = self.load_binary_image(expected_path)
  177.  
  178. for i in range(1, max_iter + 1):
  179. self.step()
  180.  
  181. if i in save_at_iterations:
  182. output_path = os.path.join(output_dir, f'{i}.png')
  183. self.save_output(output_path)
  184. print(f"Saved output at iteration {i}")
  185.  
  186. # Compare with expected output if available
  187. if i in expected_outputs:
  188. expected = expected_outputs[i]
  189. diff = np.sum(self.grid != expected)
  190. if diff > 0:
  191. diff_coords = np.where(self.grid != expected)
  192. print(f"Warning: Iteration {i} differs from expected by {diff} cells")
  193. for idx in range(min(5, len(diff_coords[0]))):
  194. y, x = diff_coords[0][idx], diff_coords[1][idx]
  195. print(f" Diff at ({y}, {x}): Got {self.grid[y, x]}, Expected {expected[y, x]}")
  196. else:
  197. print(f"Iteration {i} matches expected output exactly ✅")
  198.  
  199.  
  200. def save_output(self, output_path):
  201. """Save the current grid state as an image"""
  202. img = Image.new('L', (self.width, self.height))
  203. pixels = []
  204.  
  205. for y in range(self.height):
  206. for x in range(self.width):
  207. pixels.append(255 if self.grid[y, x] else 0)
  208.  
  209. img.putdata(pixels)
  210. img.save(output_path)
  211.  
  212. # Print the count of live cells in the output
  213. live_count = np.sum(self.grid)
  214. print(f"Live cells in {output_path}: {live_count}")
  215.  
  216. # Display coordinates of alive cells if there aren't too many
  217. if live_count > 0 and live_count <= 15:
  218. alive_coords = np.where(self.grid)
  219. print("Alive cell coordinates:")
  220. for i in range(len(alive_coords[0])):
  221. y, x = alive_coords[0][i], alive_coords[1][i]
  222. is_wormhole = (y, x) in self.wormhole_positions
  223. print(f" ({y}, {x}){' (wormhole)' if is_wormhole else ''}")
  224.  
  225. def run_simulation(input_dir, output_dir):
  226. os.makedirs(output_dir, exist_ok=True)
  227.  
  228. game = WormholeGameOfLife(input_dir)
  229.  
  230. # Save initial state (optional for debugging)
  231. game.save_output(os.path.join(output_dir, 'initial.png'))
  232.  
  233. # Define milestones where we save outputs
  234. milestones = [1, 10, 100, 1000]
  235.  
  236. # Run full simulation
  237. game.simulate(milestones, output_dir)
  238.  
  239. def print_example_differences():
  240. """Print the differences between examples to understand the wormhole impact"""
  241. import os
  242.  
  243. # Load standard Game of Life results from example-1
  244. std_img_1 = Image.open(os.path.join('std_output/example-1', '1.png')).convert('L')
  245. std_result_1 = np.array(std_img_1) > 128
  246.  
  247. # Load expected results from example-0
  248. expected_img_0 = Image.open(os.path.join('assets/archive/example-0', 'expected-1.png')).convert('L')
  249. expected_result_0 = np.array(expected_img_0) > 128
  250.  
  251. # Compare
  252. print(f"Standard Game of Life (ex-1) alive cells: {np.sum(std_result_1)}")
  253. print(f"Expected with wormholes (ex-0) alive cells: {np.sum(expected_result_0)}")
  254.  
  255. if __name__ == "__main__":
  256. if len(sys.argv) < 3:
  257. print("Usage: python wormhole_game_of_life.py <input_directory> <output_directory>")
  258. sys.exit(1)
  259.  
  260. input_dir = sys.argv[1]
  261. output_dir = sys.argv[2]
  262.  
  263. # Optionally print differences between examples
  264. # print_example_differences()
  265.  
  266. run_simulation(input_dir, output_dir)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement