csmangum / GCA

Generative Cellular Automata
Apache License 2.0
0 stars 0 forks source link

Procedural Generated CA #3

Open csmangum opened 4 months ago

csmangum commented 4 months ago

Creating a procedurally generated image that updates based on a specific cellular automata rule, with movement controls for panning through the image, involves several steps in Pygame. First, you'll need to initialize Pygame and set up the main game loop. Then, implement the cellular automata logic, handle user input for movement, and draw the grid based on the automata's state. Below is a basic example to get you started, using a very simplified automata rule for demonstration purposes.

This example assumes you're familiar with Pygame basics. The cellular automata rule used here is a placeholder; you can replace it with any rule you like, such as Conway's Game of Life or something more complex.

import pygame
import numpy as np

# Pygame setup
pygame.init()
width, height = 800, 600
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()

# Grid setup
cell_size = 10  # Size of the cells
grid_width, grid_height = width // cell_size, height // cell_size
grid = np.random.randint(2, size=(grid_width, grid_height))  # Random initial state

def update_grid():
    global grid
    new_grid = grid.copy()
    # Loop through every cell in the grid and apply the rule
    for x in range(grid_width):
        for y in range(grid_height):
            # Simple rule: A cell inverts its state if it has at least one neighbor
            neighbors = np.sum(grid[x-1:x+2, y-1:y+2]) - grid[x, y]
            new_grid[x, y] = 1 if neighbors > 0 and grid[x, y] == 0 else 0
    grid = new_grid

def draw_grid(offset_x, offset_y):
    screen.fill((255, 255, 255))  # Fill screen with white
    for x in range(grid_width):
        for y in range(grid_height):
            rect = pygame.Rect(x * cell_size + offset_x, y * cell_size + offset_y, cell_size, cell_size)
            if grid[x, y] == 1:
                pygame.draw.rect(screen, (0, 0, 0), rect)  # Draw black cell

def main():
    offset_x, offset_y = 0, 0
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # User input for movement
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            offset_y += 10
        if keys[pygame.K_s]:
            offset_y -= 10
        if keys[pygame.K_a]:
            offset_x += 10
        if keys[pygame.K_d]:
            offset_x -= 10

        update_grid()  # Update the grid for the next frame
        draw_grid(offset_x, offset_y)  # Draw the grid with the current offset

        pygame.display.flip()
        clock.tick(10)  # Limit to 10 frames per second

    pygame.quit()

if __name__ == "__main__":
    main()

Key Components Explained:

You can enhance this example by implementing more complex automata rules, optimizing performance (e.g., updating only a portion of the grid visible on the screen), or adding more controls for user interaction.

csmangum commented 4 months ago

To enhance the code, let's implement a more interactive and efficient approach. This version will include:

  1. Efficient Grid Updating: Instead of updating the entire grid every frame, we'll only update the visible portion based on the user's position.
  2. Dynamic Grid Expansion: As the user moves, the grid will dynamically expand in that direction, maintaining the illusion of an infinite procedural world.
  3. Optimization: Reduce unnecessary computations for cells that are not currently visible.

Here's an improved version of the code:

import pygame
import numpy as np

# Pygame setup
pygame.init()
width, height = 800, 600
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()

# Grid setup
cell_size = 10  # Size of the cells
grid_size = 100  # Initial grid size
grid = np.random.randint(2, size=(grid_size, grid_size))  # Random initial state

def update_visible_grid(offset_x, offset_y):
    global grid
    start_x, start_y = max(0, -offset_x // cell_size), max(0, -offset_y // cell_size)
    end_x = min(grid_size, width // cell_size - offset_x // cell_size)
    end_y = min(grid_size, height // cell_size - offset_y // cell_size)

    # Apply the rule only on the visible portion of the grid
    for x in range(start_x, end_x):
        for y in range(start_y, end_y):
            # Simple rule for demonstration
            neighbors = np.sum(grid[max(0, x-1):min(grid_size, x+2), max(0, y-1):min(grid_size, y+2)]) - grid[x, y]
            grid[x, y] = 1 if neighbors > 0 and grid[x, y] == 0 else 0

def draw_grid(offset_x, offset_y):
    screen.fill((255, 255, 255))  # Fill screen with white
    for x in range(width // cell_size):
        for y in range(height // cell_size):
            grid_x, grid_y = x + offset_x // cell_size, y + offset_y // cell_size
            if 0 <= grid_x < grid_size and 0 <= grid_y < grid_size and grid[grid_x, grid_y] == 1:
                rect = pygame.Rect(x * cell_size, y * cell_size, cell_size, cell_size)
                pygame.draw.rect(screen, (0, 0, 0), rect)  # Draw black cell

def expand_grid(direction):
    global grid, grid_size
    # Expand the grid based on the direction of movement
    if direction == "left" or direction == "right":
        new_column = np.random.randint(2, size=(grid_size, 1))
        grid = np.hstack((new_column, grid)) if direction == "left" else np.hstack((grid, new_column))
        grid_size += 1
    elif direction == "up" or direction == "down":
        new_row = np.random.randint(2, size=(1, grid_size))
        grid = np.vstack((new_row, grid)) if direction == "up" else np.vstack((grid, new_row))
        grid_size += 1

def main():
    offset_x, offset_y = 0, 0
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # User input for movement
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            offset_y += 10
            expand_grid("up")
        if keys[pygame.K_s]:
            offset_y -= 10
            expand_grid("down")
        if keys[pygame.K_a]:
            offset_x += 10
            expand_grid("left")
        if keys[pygame.K_d]:
            offset_x -= 10
            expand_grid("right")

        update_visible_grid(offset_x, offset_y)
        draw_grid(offset_x, offset_y)

        pygame.display.flip()
        clock.tick(30)  # Increased to 30 frames per second for smoother movement

    pygame.quit()

if __name__ == "__main__":
    main()

Enhancements Explained:

This code forms a more interactive and performance-efficient foundation for your procedural generation project. You can further optimize and expand upon it by integrating more complex cellular automata rules and adding features like zooming or different terrains based on cell states.