pygamesimulator

Automaton rendering/state issue


I'm working on sort of automaton where there is one red with "state = 2" square which checks neighboring squares in order up, left, down, right and if one of them is has "state = 0" it will change its to "state = 2" and their state will change to 1. The issue is, when the red square is "going" left and up it doesn't show up. Also when i changed the code to render at the begging of the update function of squares class it looked like there were 2 squares that had "state = 2".

grid_size = 50
updated = False

class squares:
    def __init__(self,position,state = 0):
        self.position = position
        self.state = state
        self.operation_done = False
    def change_state(self):
        self.state = 2
    def update(self):
        global updated
        
        if not updated:
            if self.state == 2:
                self.operation_done =False
                if self.position[1] - grid_size >= 0 and not self.operation_done:
                    up = (self.position[0], self.position[1] - grid_size)
                    neighbor_up = squares_dict[up]
                    if neighbor_up.state == 0:
                        neighbor_up.change_state()
                        self.operation_done = True
                        self.state = 1
                        updated = True
                if self.position[0] - grid_size >= 0 and not self.operation_done: 
                    left = (self.position[0] - grid_size, self.position[1])
                    neighbor_left = squares_dict[left]
                    if neighbor_left.state == 0:
                        neighbor_left.change_state()
                        self.operation_done = True
                        self.state = 1
                        updated = True
                if self.position[1] + grid_size <= 450 and not self.operation_done: 
                    down = (self.position[0], self.position[1] + grid_size)
                    neighbor_down = squares_dict[down]
                    if neighbor_down.state == 0:
                        neighbor_down.change_state()
                        self.operation_done = True
                        self.state = 1
                        updated = True
                if self.position[0] + grid_size <= 450 and not self.operation_done: 
                    right = (self.position[0] + grid_size, self.position[1])
                    neighbor_right = squares_dict[right]
                    if neighbor_right.state == 0:
                        neighbor_right.change_state()
                        self.operation_done = True
                        self.state = 1
                        updated = True
                        
        if self.state == 2:
            pygame.draw.rect(screen_display,(255,0,0), (*self.position,grid_size,grid_size))
        elif self.state == 1:
            pygame.draw.rect(screen_display,(255,255,255), (*self.position,grid_size,grid_size))
        elif self.state == 0:
            pygame.draw.rect(screen_display,(0,0,0), (*self.position,grid_size,grid_size))

I have no idea whats the issue as it works right when it "goes" right and down.


Solution

  • I think the issue is that your code is advancing the state of the square while it's still processing the world. So say you update the east-neighbour, but the very next cell to process is x + 1, and the value of the state has just been reset!

    You need to keep the "current state" and the "next, calculated state" separate.

    What I have implemented below is keeping two states, self.state and self.next_state. As the update-algorithm proceeds through the grid, tests are made on self.state but changes stored in self.next_state. Once the entire grid-world is processed, then squares can switch to the next state.

    I had to guess at what your program was supposed to do, maybe I got those parts wrong. Apologies in advance.

    screen shot

    import pygame
    import random
    
    # Window size
    WINDOW_WIDTH  = 500    
    WINDOW_HEIGHT = 500
    
    UPDATE_MILLIS = 1200 # time between updates
    GRID_CELLS = 10      # N x N sized world-grid
    GRID_SIZE = 50       # How biug cells are drawn in the window
    
    class Square:
        def __init__(self,position,state = 0):
            self.position = position      # ( column, row ) position in the world grid
            self.state    = state         # Current state used for calculations
            self.next_state = state       # The next state we become
            # References to our neighbours, with grid wrap-around
            self.north = None
            self.east  = None
            self.south = None
            self.west  = None
    
        def map_neighbours( self, world_grid, grid_width, grid_height ):
            """ Link references to other cells in the grid """
            x, y = self.position
            # NORTH
            if ( y == 0 ):
                self.north = world_grid[grid_height-1][x]   # wrap-around top->bottom
            else:
                self.north = world_grid[y-1][x]
            # EAST
            if ( x == grid_width-1 ):
                self.east = world_grid[y][0]
            else:
                self.east = world_grid[y][x+1]
            # SOUTH
            if ( y == grid_height-1 ):
                self.south = world_grid[0][x]
            else:
                self.south = world_grid[y+1][x]  
            # WEST
            if ( x == 0 ):
                self.west = world_grid[y][grid_width-1]
            else:
                self.west = world_grid[y][x-1]
    
        def update(self):
            # Deternime the next state.
            #     If I am state==2, and my neighbour is state==0 -> switch to state:=1
            neighbours = [ self.north, self.east, self.south, self.west ]
            for neighbour in neighbours:
                if ( self.state == 2 and neighbour.state == 0 ):
                    self.next_state = 1
                    neighbour.next_state = 2 # TODO: re-write in terms of "my" state, don't change neighbour, it should change itself
    
        def advance_state( self ):
            """ Move to the target state """
            self.state = self.next_state
    
        def __str__( self ):
            s = "Square at (%3d,%3d) state=%d" % ( self.position[0], self.position[1], self.state )
            return s
    
        def draw( self, surface ):
            if self.state == 2:
                colour = (255,0,0)
            elif self.state == 1:
                colour = (255,255,255)
            elif self.state == 0:
                colour = (0,0,0)
            else:
                colour = (0,255,255)  # error, should not happen
    
            x,y = self.position
            pygame.draw.rect( surface, colour, (x*GRID_SIZE, y*GRID_SIZE, GRID_SIZE, GRID_SIZE ) )
    
    
    
    
    ###
    ### MAIN
    ###
    pygame.init()
    window  = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), pygame.HWSURFACE )
    pygame.display.set_caption("Squares")
    
    
    # Define some Squares in a World
    world_grid = []
    for y in range( GRID_CELLS ):
        world_grid.append( [] )  # empty row
        for x in range( GRID_CELLS ):
            position = ( x, y ) 
            state = random.randint( 0, 2 )
            world_grid[y].append( Square( position, state ) )
            #print( "Added Square: " + str( world_grid[y][-1] ) )
    
    # we can't find our neighbours until all cells are filled
    for y in range( GRID_CELLS ):
        for x in range( GRID_CELLS ):
            world_grid[y][x].map_neighbours( world_grid, GRID_CELLS, GRID_CELLS )
    
    
    # Main loop
    clock = pygame.time.Clock()
    next_update = pygame.time.get_ticks() + UPDATE_MILLIS  # time in future to update states
    running = True
    while running:
        time_now = pygame.time.get_ticks()
    
        # Handle user-input
        for event in pygame.event.get():
            if ( event.type == pygame.QUIT ):
                running = False
    
        # Update the state-transition algorithm every period
        if ( time_now > next_update ):
            next_update = time_now + UPDATE_MILLIS  # reschedule next update
            # Do the update
            for y_row in world_grid:
                for sq in y_row:
                    sq.update()     # calculate all the next states
            for y_row in world_grid:
                for sq in y_row:
                    sq.advance_state()   # now calc is done, switch to new state (if any)
    
        # Draw and update the world_grid
        for y_row in world_grid:
            for sq in y_row:
                sq.draw( window )
    
        pygame.display.flip()
    
        # Clamp FPS
        clock.tick(60)
    
    pygame.quit()