Exemple #1
0
    def __init__(self, width, height, margin, scale, speed, loop):
        '''
        scale:
            each cell in the maze will be a square of (scale x scale) pixels in the image.

        speed:
            control how often a frame is rendered.

        loop:
            the number of loops of the GIF image.

        delay:
            the delay between two successive frames.

        trans_index:
            which transparent color is used.

        colormap:
            a dict that maps the maze to an image.
        '''

        Maze.__init__(self, width, height, margin)
        self.writer = GIFWriter(width * scale, height * scale, loop)
        self.scale = scale
        self.speed = speed
        self.trans_index = 3
        self.delay = 2
        self.colormap = {i: i for i in range(4)}
Exemple #2
0
    def __init__(self, maze, scale, offsets, depth, palette, loop, filename):
        """
        INPUTS:

            - `maze`: an instance of the maze class below.

            - `scale`: each cell in the maze occupies scale*scale pixels in the image.

            - `offsets`: only useful if this canvas is embedded into another gif image.
                         It's a tuple of 4 integers that specify the (left, right, top, bottom)
                         offsets of this canvas relative to that image.

            - `filename`: the output file.

            - `depth`, `palette`, `loop`: the same as they are in the GIFWriter class.
        """
        self.maze = maze
        self.scale = scale
        self.writer = GIFWriter(maze.width * scale + offsets[0] + offsets[1],
                                maze.height * scale + offsets[2] + offsets[3],
                                depth, palette, loop)
        self.offsets = offsets
        # use a dict to map the cells to the color indices.
        self.colormap = {i: i for i in range(1 << depth)}
        self.speed = 10  # output the frame once this number of cells are changed.
        self.trans_index = 3  # the index of the transparent color in the global color table.
        self.delay = 5  # delay between successive frames.
        self.target_file = open(filename, 'wb')
        self.target_file.write(self.writer.logical_screen_descriptor +
                               self.writer.global_color_table +
                               self.writer.loop_control)
Exemple #3
0
    def __init__(self, maze, scale=5, speed=30, loop=0):
        self.maze = maze
        self.writer = GIFWriter(maze.width * scale, maze.height * scale, loop)
        self.scale = scale
        self.speed = speed
        self.trans_index = 3
        self.delay = 5

        # this dict is used for communicating with our LZW encoder.
        # by modifying it we can color a maze in different ways.
        self.init_table = {str(c): c for c in range(4)}
Exemple #4
0
    def __init__(self, width, height, scale, min_bits, palette, loop):
        self.width = width
        self.height = height
        self.grid = [[0] * height for _ in range(width)]
        self.scale = scale
        self.num_changes = 0  # a counter holds how many cells are changed.
        self.frame_box = None  # maintains the region that to be updated.
        self.writer = GIFWriter(width * scale, height * scale, min_bits,
                                palette, loop)
        self.colormap = {i: i for i in range(1 << min_bits)}

        self.speed = 10  # output the frame once this number of cells are changed.
        self.trans_index = 3  # the index of the transparent color in the global color table.
        self.delay = 5  # delay between successive frames.
Exemple #5
0
    def __init__(self, width, height, margin, scale, speed, loop):
        '''
        scale: size of a cell in pixels.
        speed: speed of the animation.
        loop: number of loops.
        '''
        Maze.__init__(self, width, height, margin)
        self.writer = GIFWriter(width * scale, height * scale, loop)
        self.scale = scale
        self.speed = speed
        self.trans_index = 3

        # this table is used for communicating with our LZW encoder.
        # by modifying it we can color a maze in different ways.
        self.init_table = {str(c): c for c in range(4)}
Exemple #6
0
    def encode_frame(self):
        if self.frame_box is not None:
            left, top, right, bottom = self.frame_box
        else:
            left, top, right, bottom = 0, 0, self.width - 1, self.height - 1

        width = right - left + 1
        height = bottom - top + 1
        descriptor = GIFWriter.image_descriptor(left * self.scale,
                                                top * self.scale,
                                                width * self.scale,
                                                height * self.scale)

        def get_frame_pixels():
            for i in range(width * height * self.scale * self.scale):
                y = i // (width * self.scale * self.scale)
                x = (i % (width * self.scale)) // self.scale
                val = self.grid[x + left][y + top]
                c = self.colormap[val]
                yield c

        frame = self.writer.LZW_encode(get_frame_pixels())
        self.num_changes = 0
        self.frame_box = None
        return descriptor + frame
    def encode_frame(self, static=False):
        """
        Encode current maze into one frame.
        If static is `True` then the graphics control block is not added
        (so this frame can be used as a static background image).
        """
        # get the bounding box of this frame.
        if self.maze.frame_box is not None:
            left, top, right, bottom = self.maze.frame_box
        else:
            left, top, right, bottom = 0, 0, self.maze.width - 1, self.maze.height - 1

        # then get the image descriptor of this frame.
        width = right - left + 1
        height = bottom - top + 1
        descriptor = GIFWriter.image_descriptor(left * self.scale,
                                                top * self.scale,
                                                width * self.scale,
                                                height * self.scale)

        # A generator that yields the pixels of this frame. This may look a bit unintuitive
        # because encoding frames will be called thousands of times in an animation
        # and I don't want to create and destroy a new list each time it's called.
        def get_frame_pixels():
            for i in range(width * height * self.scale * self.scale):
                y = i // (width * self.scale * self.scale)
                x = (i % (width * self.scale)) // self.scale
                val = self.maze.grid[x + left][y + top]
                c = self.colormap[val]
                yield c

        # encode the frame data via the LZW compression.
        frame = self.writer.LZW_encode(get_frame_pixels())

        # reset `num_changes` and `frame_box`.
        self.maze.num_changes = 0
        self.maze.frame_box = None

        if static:
            return descriptor + frame
        else:
            control = GIFWriter.graphics_control_block(self.delay,
                                                       self.trans_index)
            return control + descriptor + frame
 def __init__(self, maze, scale, min_bits, palette, loop, filename):
     """
     maze: an instance of the maze class below.
     scale: each cell in the maze occupies scale*scale pixels in the image.
     filename: the output file.
     min_bits, palette, loop: the same as they are in the GIFWriter class.
     """
     self.maze = maze
     self.scale = scale
     self.writer = GIFWriter(maze.width * scale, maze.height * scale,
                             min_bits, palette, loop)
     # use a dict to map the cells to the color indices.
     self.colormap = {i: i for i in range(1 << min_bits)}
     self.speed = 10  # output the frame once this number of cells are changed.
     self.trans_index = 3  # the index of the transparent color in the global color table.
     self.delay = 5  # delay between successive frames.
     self.target_file = open(filename, 'wb')
     self.target_file.write(self.writer.logical_screen_descriptor +
                            self.writer.global_color_table +
                            self.writer.loop_control)
    def __init__(self, width, height, margin, scale, speed, loop):
        '''
        scale: size of a cell in pixels.
        speed: speed of the animation.
        loop: number of loops.
        '''
        Maze.__init__(self, width, height, margin)
        self.writer = GIFWriter(width * scale, height * scale, loop)
        self.scale = scale
        self.speed = speed
        self.trans_index = 3

        # this table is used for communicating with our LZW encoder.
        # by modifying it we can color a maze in different ways.
        self.init_table = {str(c): c for c in range(4)}
Exemple #10
0
class WilsonAnimation(Maze):
    '''
    our animation contains basically two parts:
    run the algorithm, and write to the file.

    to write to the file, we will need several attributes:
    1. delay: control the delay between frames.
    2. trans_index: control which transparent color is used.
    3. init_dict: map the maze into an image (to communicate with our LZW encoder)

    to run the animation, we will need these data structures:
    1. self.tree: maintain the cells that have been added to the tree.
    2. self.path: maintain the path of the loop erased random walk.
    '''
    def __init__(self, width, height, margin, scale, speed, loop):
        '''
        scale: size of a cell in pixels.
        speed: speed of the animation.
        loop: number of loops.
        '''
        Maze.__init__(self, width, height, margin)
        self.writer = GIFWriter(width * scale, height * scale, loop)
        self.scale = scale
        self.speed = speed
        self.trans_index = 3

        # this table is used for communicating with our LZW encoder.
        # by modifying it we can color a maze in different ways.
        self.init_table = {str(c): c for c in range(4)}

    def __call__(self, filename):

        # here we need to paint the blank background because the region that has not been
        # covered by any frame will be set to transparent by decoders.
        # comment this line and watch the result if you don't understand this.
        self.paint_background()

        # pad a two-seconds delay, get ready!
        self.pad_delay_frame(200)

        # in the wilson algorithm step no cells are 'filled',
        # hence it's safe to use color 3 as the transparent color.
        self.make_wison_animation(delay=2,
                                  trans_index=3,
                                  wall_color=0,
                                  tree_color=1,
                                  path_color=2)

        # pad a three-seconds delay to help to see the resulting maze clearly.
        self.pad_delay_frame(300)

        # fix a suitable speed for path finding animation.
        self.set_speed(10)

        # in the dfs algorithm step the walls are unchanged throughout,
        # hence it's safe to use color 0 as the transparent color.
        self.make_dfs_animation(delay=5,
                                trans_index=0,
                                wall_color=0,
                                tree_color=0,
                                path_color=2,
                                fill_color=3)

        # pad a five-seconds delay to help to see the resulting path clearly.
        self.pad_delay_frame(500)
        # finally save the bits stream in 'wb' mode.
        self.write_to_gif(filename)

    def make_wison_animation(self, delay, trans_index, **kwargs):
        '''
        animating the Wilson algorithm.
        '''
        self.set_delay(delay)
        self.set_transparent(trans_index)
        self.set_colors(**kwargs)

        # initially the tree only contains the root.
        self.init_tree(self.start)

        # for each cell in the maze that is not in the tree yet,
        # start a loop erased random walk from this cell until the walk hits the tree.
        for cell in self.cells:
            if cell not in self.tree:
                self.loop_erased_random_walk(cell)

        # there may be some changes that has not been written to the file, write them.
        self.clear()

    def init_tree(self, root):
        self.tree = set([root])
        self.mark_cell(root, TREE)

    def loop_erased_random_walk(self, cell):
        '''
        start a loop erased random walk from this cell until it hits the tree.
        '''
        self.begin_path(cell)
        current_cell = cell

        while current_cell not in self.tree:
            current_cell = self.move_one_step(current_cell)
            self.refresh_frame()

        # once the walk meets the tree, add the path to the tree.
        self.tree = self.tree.union(self.path)
        self.mark_path(self.path, TREE)

    def begin_path(self, cell):
        self.path = [cell]
        self.mark_cell(cell, PATH)

    def move_one_step(self, cell):
        '''
        the most fundamental operation in wilson algorithm:
        choose a random neighbor z of current cell, and move to z.

        1. if z already in current path, then a loop is found, erase this loop
           and start the walk from z again.

        2. if z is not in current path, then add z to current path.

        repeat this procedure until z 'hits' the tree.
        '''
        next_cell = random.choice(self.get_neighbors(cell))

        # if next_cell is already in path, then we have found a loop in our path, erase it!
        if next_cell in self.path:
            self.erase_loop(next_cell)
        else:
            self.add_to_path(next_cell)
        return next_cell

    def erase_loop(self, cell):
        index = self.path.index(cell)

        # erase the loop
        self.mark_path(self.path[index:], WALL)

        # re-mark this cell
        self.mark_cell(self.path[index], PATH)

        self.path = self.path[:index + 1]

    def add_to_path(self, cell):
        self.mark_cell(cell, PATH)
        self.mark_wall(self.path[-1], cell, PATH)
        self.path.append(cell)

    def make_dfs_animation(self, delay, trans_index, **kwargs):
        '''
        animating the depth first search algorithm.
        '''
        self.set_delay(delay)
        self.set_transparent(trans_index)
        self.set_colors(**kwargs)

        # besides a stack to run the dfs, we need a dict to remember each step.
        from_to = dict()
        stack = [(self.start, self.start)]
        visited = set([self.start])

        while stack:
            parent, child = stack.pop()
            from_to[parent] = child
            self.mark_cell(child, FILL)
            self.mark_wall(parent, child, FILL)

            if child == self.end:
                break
            else:
                for next_cell in self.get_neighbors(child):
                    if (next_cell not in visited) and (not self.check_wall(
                            child, next_cell)):
                        stack.append((child, next_cell))
                        visited.add(next_cell)

            self.refresh_frame()
        self.clear()

        # retrieve the path
        path = [self.start]
        tmp = self.start
        while tmp != self.end:
            tmp = from_to[tmp]
            path.append(tmp)

        self.mark_path(path, PATH)
        # show the path
        self.refresh_frame()

    def set_transparent(self, index):
        self.trans_index = index

    def set_delay(self, delay):
        self.delay = delay

    def set_speed(self, speed):
        self.speed = speed

    def set_colors(self, **kwargs):
        colormap = {
            'wall_color': '0',
            'tree_color': '1',
            'path_color': '2',
            'fill_color': '3'
        }
        for key, val in kwargs.items():
            self.init_table[colormap[key]] = val

    def pad_delay_frame(self, delay):
        self.writer.data += self.writer.pad_delay_frame(
            delay, self.trans_index)

    def refresh_frame(self):
        if self.num_changes >= self.speed:
            self.write_current_frame()

    def clear(self):
        '''
        if there are remaining changes that has not been rendered, output them.
        '''
        if self.num_changes > 0:
            self.write_current_frame()

    def write_current_frame(self):
        control = self.writer.graphics_control_block(self.delay,
                                                     self.trans_index)
        self.writer.data += control + self.encode_frame()

    def paint_background(self, **kwargs):
        '''
        if no colors are specified then previous self.init_table will be used.
        this function allows you to insert current frame at the beginning of the file
        to serve as the background, it does not need the graphics control block.
        '''
        if kwargs:
            self.set_colors(**kwargs)
        else:
            self.writer.data = self.encode_frame() + self.writer.data

    def encode_frame(self):
        '''
        encode the frame, but not write the result to the stream.
        '''
        if self.frame_box:
            left, top, right, bottom = self.frame_box
        else:
            left, top, right, bottom = 0, 0, self.width - 1, self.height - 1

        width = right - left + 1
        height = bottom - top + 1
        descriptor = self.writer.image_descriptor(left * self.scale,
                                                  top * self.scale,
                                                  width * self.scale,
                                                  height * self.scale)

        input_data = [0] * width * height * self.scale * self.scale
        for i in range(len(input_data)):
            y = i // (width * self.scale * self.scale)
            x = (i % (width * self.scale)) // self.scale
            input_data[i] = self.grid[x + left][y + top]

        self.num_changes = 0
        self.frame_box = None
        return descriptor + self.writer.LZW_encode(input_data, self.init_table)

    def write_to_gif(self, filename):
        self.writer.save(filename)
class WilsonAnimation(Maze):

    '''
    our animation contains basically two parts:
    run the algorithm, and write to the file.

    to write to the file, we will need several attributes:
    1. delay: control the delay between frames.
    2. trans_index: control which transparent color is used.
    3. init_dict: map the maze into an image (to communicate with our LZW encoder)

    to run the animation, we will need these data structures:
    1. self.tree: maintain the cells that have been added to the tree.
    2. self.path: maintain the path of the loop erased random walk.
    '''

    def __init__(self, width, height, margin, scale, speed, loop):
        '''
        scale: size of a cell in pixels.
        speed: speed of the animation.
        loop: number of loops.
        '''
        Maze.__init__(self, width, height, margin)
        self.writer = GIFWriter(width * scale, height * scale, loop)
        self.scale = scale
        self.speed = speed
        self.trans_index = 3

        # this table is used for communicating with our LZW encoder.
        # by modifying it we can color a maze in different ways.
        self.init_table = {str(c): c for c in range(4)}


    def __call__(self, filename):

        # here we need to paint the blank background because the region that has not been
        # covered by any frame will be set to transparent by decoders.
        # comment this line and watch the result if you don't understand this.
        self.paint_background()

        # pad a two-seconds delay, get ready!
        self.pad_delay_frame(200)

        # in the wilson algorithm step no cells are 'filled',
        # hence it's safe to use color 3 as the transparent color.
        self.make_wison_animation(delay=2, trans_index=3,
                                  wall_color=0, tree_color=1, path_color=2)

        # pad a three-seconds delay to help to see the resulting maze clearly.
        self.pad_delay_frame(300)

        # fix a suitable speed for path finding animation.
        self.set_speed(10)

        # in the dfs algorithm step the walls are unchanged throughout,
        # hence it's safe to use color 0 as the transparent color.
        self.make_dfs_animation(delay=5, trans_index=0, wall_color=0,
                                tree_color=0, path_color=2, fill_color=3)

        # pad a five-seconds delay to help to see the resulting path clearly.
        self.pad_delay_frame(500)
        # finally save the bits stream in 'wb' mode.
        self.write_to_gif(filename)


    def make_wison_animation(self, delay, trans_index, **kwargs):
        '''
        animating the Wilson algorithm.
        '''
        self.set_delay(delay)
        self.set_transparent(trans_index)
        self.set_colors(**kwargs)

        # initially the tree only contains the root.
        self.init_tree(self.start)

        # for each cell in the maze that is not in the tree yet,
        # start a loop erased random walk from this cell until the walk hits the tree.
        for cell in self.cells:
            if cell not in self.tree:
                self.loop_erased_random_walk(cell)

        # there may be some changes that has not been written to the file, write them.
        self.clear()


    def init_tree(self, root):
        self.tree = set([root])
        self.mark_cell(root, TREE)


    def loop_erased_random_walk(self, cell):
        '''
        start a loop erased random walk from this cell until it hits the tree.
        '''
        self.begin_path(cell)
        current_cell = cell

        while current_cell not in self.tree:
            current_cell = self.move_one_step(current_cell)
            self.refresh_frame()

        # once the walk meets the tree, add the path to the tree.
        self.tree = self.tree.union(self.path)
        self.mark_path(self.path, TREE)


    def begin_path(self, cell):
        self.path = [cell]
        self.mark_cell(cell, PATH)


    def move_one_step(self, cell):
        '''
        the most fundamental operation in wilson algorithm:
        choose a random neighbor z of current cell, and move to z.

        1. if z already in current path, then a loop is found, erase this loop
           and start the walk from z again.

        2. if z is not in current path, then add z to current path.

        repeat this procedure until z 'hits' the tree.
        '''
        next_cell = random.choice(self.get_neighbors(cell))

        # if next_cell is already in path, then we have found a loop in our path, erase it!
        if next_cell in self.path:
            self.erase_loop(next_cell)
        else:
            self.add_to_path(next_cell)
        return next_cell


    def erase_loop(self, cell):
        index = self.path.index(cell)

        # erase the loop
        self.mark_path(self.path[index:], WALL)

        # re-mark this cell
        self.mark_cell(self.path[index], PATH)

        self.path = self.path[:index+1]


    def add_to_path(self, cell):
        self.mark_cell(cell, PATH)
        self.mark_wall(self.path[-1], cell, PATH)
        self.path.append(cell)


    def make_dfs_animation(self, delay, trans_index, **kwargs):
        '''
        animating the depth first search algorithm.
        '''
        self.set_delay(delay)
        self.set_transparent(trans_index)
        self.set_colors(**kwargs)

        # besides a stack to run the dfs, we need a dict to remember each step.
        from_to = dict()
        stack = [(self.start, self.start)]
        visited = set([self.start])

        while stack:
            parent, child = stack.pop()
            from_to[parent] = child
            self.mark_cell(child, FILL)
            self.mark_wall(parent, child, FILL)

            if child == self.end:
                break
            else:
                for next_cell in self.get_neighbors(child):
                    if (next_cell not in visited) and (not self.check_wall(child, next_cell)):
                        stack.append((child, next_cell))
                        visited.add(next_cell)

            self.refresh_frame()
        self.clear()

        # retrieve the path
        path = [self.start]
        tmp = self.start
        while tmp != self.end:
            tmp = from_to[tmp]
            path.append(tmp)

        self.mark_path(path, PATH)
        # show the path
        self.refresh_frame()


    def set_transparent(self, index):
        self.trans_index = index


    def set_delay(self, delay):
        self.delay = delay


    def set_speed(self, speed):
        self.speed = speed


    def set_colors(self, **kwargs):
        colormap = {'wall_color': '0', 'tree_color': '1',
                    'path_color': '2', 'fill_color': '3'}
        for key, val in kwargs.items():
            self.init_table[colormap[key]] = val


    def pad_delay_frame(self, delay):
        self.writer.data += self.writer.pad_delay_frame(delay, self.trans_index)


    def refresh_frame(self):
        if self.num_changes >= self.speed:
            self.write_current_frame()


    def clear(self):
        '''
        if there are remaining changes that has not been rendered, output them.
        '''
        if self.num_changes > 0:
            self.write_current_frame()


    def write_current_frame(self):
        control = self.writer.graphics_control_block(self.delay, self.trans_index)
        self.writer.data += control + self.encode_frame()


    def paint_background(self, **kwargs):
        '''
        if no colors are specified then previous self.init_table will be used.
        this function allows you to insert current frame at the beginning of the file
        to serve as the background, it does not need the graphics control block.
        '''
        if kwargs:
            self.set_colors(**kwargs)
        else:
            self.writer.data = self.encode_frame() + self.writer.data


    def encode_frame(self):
        '''
        encode the frame, but not write the result to the stream.
        '''
        if self.frame_box:
            left, top, right, bottom = self.frame_box
        else:
            left, top, right, bottom = 0, 0, self.width - 1, self.height - 1

        width = right - left + 1
        height = bottom - top + 1
        descriptor = self.writer.image_descriptor(left * self.scale, top * self.scale,
                                                  width * self.scale, height * self.scale)

        input_data = [0] * width * height * self.scale * self.scale
        for i in range(len(input_data)):
            y = i // (width * self.scale * self.scale)
            x = (i % (width * self.scale)) // self.scale
            input_data[i] = self.grid[x + left][y + top]

        self.num_changes = 0
        self.frame_box = None
        return descriptor + self.writer.LZW_encode(input_data, self.init_table)


    def write_to_gif(self, filename):
        self.writer.save(filename)
Exemple #12
0
class Canvas(object):
    """
    A canvas is built on top of a maze for encoding it into frames.
    The core part is the `encode_frame` method below. Other methods
    are either wrappers of this function or simply for setting the
    parameters of the animation.
    """

    def __init__(self, maze, scale, min_bits, palette, loop, filename):
        """
        INPUTS:

            - `maze`: an instance of the maze class below.

            - `scale`: each cell in the maze occupies scale*scale pixels in the image.

            - `filename`: the output file.

            - `min_bits`, `palette`, `loop`: the same as they are in the GIFWriter class.
        """
        self.maze = maze
        self.scale = scale
        self.writer = GIFWriter(maze.width * scale, maze.height * scale, min_bits, palette, loop)
        # use a dict to map the cells to the color indices.
        self.colormap = {i: i for i in range(1 << min_bits)}
        self.speed = 10        # output the frame once this number of cells are changed.
        self.trans_index = 3   # the index of the transparent color in the global color table.
        self.delay = 5         # delay between successive frames.
        self.target_file = open(filename, 'wb')
        self.target_file.write(self.writer.logical_screen_descriptor
                               + self.writer.global_color_table
                               + self.writer.loop_control)
        
    def encode_frame(self, static=False):
        """
        Encode current maze into one frame.
        If static is `True` then the graphics control block is not added
        (so this frame can be used as a static background image).
        """
        # get the bounding box of this frame.
        if self.maze.frame_box is not None:
            left, top, right, bottom = self.maze.frame_box
        else:
            left, top, right, bottom = 0, 0, self.maze.width - 1, self.maze.height - 1

        # then get the image descriptor of this frame.
        width = right - left + 1
        height = bottom - top + 1
        descriptor = GIFWriter.image_descriptor(left * self.scale, top * self.scale,
                                                width * self.scale, height * self.scale)

        # A generator that yields the pixels of this frame. This may look a bit unintuitive
        # because encoding frames will be called thousands of times in an animation
        # and I don't want to create and destroy a new list each time it's called. 
        def get_frame_pixels():
            for i in range(width * height * self.scale * self.scale):
                y = i // (width * self.scale * self.scale)
                x = (i % (width * self.scale)) // self.scale
                val = self.maze.grid[x + left][y + top]
                c = self.colormap[val]
                yield c

        # encode the frame data via the LZW compression.
        frame = self.writer.LZW_encode(get_frame_pixels())

        # reset `num_changes` and `frame_box`.
        self.maze.num_changes = 0
        self.maze.frame_box = None

        if static:
            return descriptor + frame
        else:
            control = GIFWriter.graphics_control_block(self.delay, self.trans_index)
            return control + descriptor + frame

    def paint_background(self, **kwargs):
        """
        Insert current frame at the beginning to use it as the background.
        This does not require the graphics control block.
        """
        if kwargs:
            self.set_colors(**kwargs)
        self.target_file.write(self.encode_frame(static=True))

    def refresh_frame(self):
        """Update a frame in the animation and write it into the file."""
        if self.maze.num_changes >= self.speed:
            self.target_file.write(self.encode_frame(static=False))

    def clear_remaining_changes(self):
        """May be there are some remaining changes when the animation is finished."""
        if self.maze.num_changes > 0:
            self.target_file.write(self.encode_frame(static=False))

    def set_colors(self, **kwargs):
        color_dict = {'wall_color': 0, 'tree_color': 1,
                      'path_color': 2, 'fill_color': 3}
        for key, val in kwargs.items():
            self.colormap[color_dict[key]] = val

    def pad_delay_frame(self, delay):
        self.target_file.write(self.writer.pad_delay_frame(delay, self.trans_index))
        
    def set_control_params(self, speed=30, delay=3, trans_index=5, **kwargs):
        self.speed = speed
        self.delay = delay
        self.trans_index = trans_index
        self.set_colors(**kwargs)

    def save(self):
        self.target_file.write(bytearray([0x3B]))
        self.target_file.close()
Exemple #13
0
class WilsonAlgoAnimation(Maze):
    '''
    Our animation contains basically two parts: run the algorithms, and write to the GIF file.
    '''

    def __init__(self, width, height, margin, scale, speed, loop):
        '''
        scale:
            each cell in the maze will be a square of (scale x scale) pixels in the image.

        speed:
            control how often a frame is rendered.

        loop:
            the number of loops of the GIF image.

        delay:
            the delay between two successive frames.

        trans_index:
            which transparent color is used.

        colormap:
            a dict that maps the maze to an image.
        '''

        Maze.__init__(self, width, height, margin)
        self.writer = GIFWriter(width * scale, height * scale, loop)
        self.scale = scale
        self.speed = speed
        self.trans_index = 3
        self.delay = 2
        self.colormap = {i: i for i in range(4)}


    def run_wilson_algorithm(self, delay, trans_index, **kwargs):
        '''
        Animating Wilson's uniform spanning tree algorithm.
        '''
        self.set_delay(delay)
        self.set_transparent(trans_index)
        self.set_colors(**kwargs)

        # initially the tree only contains the root.
        self.mark_cell(self.start, TREE)
        self.tree = set([self.start])

        # for each cell that is not in the tree,
        # start a loop erased random walk from this cell until the walk hits the tree.
        for cell in self.cells:
            if cell not in self.tree:
                self.loop_erased_random_walk(cell)

        self.clear_remaining_changes()


    def loop_erased_random_walk(self, cell):
        '''
        Start a loop erased random walk.
        '''
        self.path = [cell]
        self.mark_cell(cell, PATH)
        current_cell = cell

        while current_cell not in self.tree:
            current_cell = self.move_one_step(current_cell)
            self.refresh_frame()

        # once the walk meets the tree, add the path to the tree.
        self.mark_path(self.path, TREE)
        self.tree = self.tree.union(self.path)


    def move_one_step(self, cell):
        '''
        The most fundamental step in Wilson's algorithm:

        1. choose a random neighbor z of current cell and move to z.
        2. (i) if z is already in current path then a loop is found, erase this loop
           and continue the walk from z.
           (ii) if z is not in current path then append it to current path.
           in both cases current cell is updated to be z.
        3. repeat this procedure until z 'hits' the tree.
        '''
        next_cell = random.choice(self.get_neighbors(cell))

        if next_cell in self.path:
            self.erase_loop(next_cell)
        else:
            self.add_to_path(next_cell)

        return next_cell


    def erase_loop(self, cell):
        index = self.path.index(cell)
        # erase the loop
        self.mark_path(self.path[index:], WALL)
        # re-mark this cell
        self.mark_cell(self.path[index], PATH)
        self.path = self.path[:index+1]


    def add_to_path(self, cell):
        self.mark_cell(cell, PATH)
        self.mark_wall(self.path[-1], cell, PATH)
        self.path.append(cell)


    def run_dfs_algorithm(self, delay, trans_index, **kwargs):
        '''
        Animating the depth first search algorithm.
        '''
        self.set_delay(delay)
        self.set_transparent(trans_index)
        self.set_colors(**kwargs)

        # we use a dict to remember each step.
        from_to = dict()
        stack = [(self.start, self.start)]
        visited = set([self.start])

        while stack:
            parent, child = stack.pop()
            from_to[parent] = child
            self.mark_cell(child, FILL)
            self.mark_wall(parent, child, FILL)

            if child == self.end:
                break
            else:
                for next_cell in self.get_neighbors(child):
                    if (next_cell not in visited) and (not self.check_wall(child, next_cell)):
                        stack.append((child, next_cell))
                        visited.add(next_cell)

            self.refresh_frame()
        self.clear_remaining_changes()

        # retrieve the path
        path = [self.start]
        tmp = self.start
        while tmp != self.end:
            tmp = from_to[tmp]
            path.append(tmp)

        self.mark_path(path, PATH)
        # show the path
        self.refresh_frame()


    def set_transparent(self, index):
        self.trans_index = index


    def set_delay(self, delay):
        self.delay = delay


    def set_speed(self, speed):
        self.speed = speed


    def set_colors(self, **kwargs):
        cell_index = {'wall_color': 0, 'tree_color': 1,
                      'path_color': 2, 'fill_color': 3}
        for key, val in kwargs.items():
            self.colormap[cell_index[key]] = val


    def pad_delay_frame(self, delay):
        self.writer.data += self.writer.pad_delay_frame(delay, self.trans_index)


    def encode_frame(self):
        '''
        Encode current maze into a frame of the GIF file.
        '''
        if self.frame_box:
            left, top, right, bottom = self.frame_box
        else:
            left, top, right, bottom = 0, 0, self.width - 1, self.height - 1

        width = right - left + 1
        height = bottom - top + 1
        descriptor = self.writer.image_descriptor(left * self.scale, top * self.scale,
                                                  width * self.scale, height * self.scale)

        # flatten the pixels of the region into a 1D list.
        input_data = [0] * width * height * self.scale * self.scale
        for i in range(len(input_data)):
            y = i // (width * self.scale * self.scale)
            x = (i % (width * self.scale)) // self.scale
            value = self.grid[x + left][y + top]
            # map the value of the cell to the color index.
            input_data[i] = self.colormap[value]

        # and don't forget to reset frame_box and num_changes.
        self.num_changes = 0
        self.frame_box = None
        return descriptor + self.writer.LZW_encode(input_data)


    def write_current_frame(self):
        control = self.writer.graphics_control_block(self.delay, self.trans_index)
        self.writer.data += control + self.encode_frame()


    def refresh_frame(self):
        if self.num_changes >= self.speed:
            self.write_current_frame()


    def clear_remaining_changes(self):
        '''
        Output (possibly) remaining changes.
        '''
        if self.num_changes > 0:
            self.write_current_frame()


    def paint_background(self, **kwargs):
        '''
        If no colors are specified then previous init_table will be used.
        This function allows you to insert current frame at the beginning of the file
        to serve as the background, it does not need the graphics control block.
        '''
        if kwargs:
            self.set_colors(**kwargs)

        self.writer.data = self.encode_frame() + self.writer.data


    def write_to_gif(self, filename):
        self.writer.save(filename)
Exemple #14
0
class Canvas(object):
    def __init__(self, width, height, scale, min_bits, palette, loop):
        self.width = width
        self.height = height
        self.grid = [[0] * height for _ in range(width)]
        self.scale = scale
        self.num_changes = 0  # a counter holds how many cells are changed.
        self.frame_box = None  # maintains the region that to be updated.
        self.writer = GIFWriter(width * scale, height * scale, min_bits,
                                palette, loop)
        self.colormap = {i: i for i in range(1 << min_bits)}

        self.speed = 10  # output the frame once this number of cells are changed.
        self.trans_index = 3  # the index of the transparent color in the global color table.
        self.delay = 5  # delay between successive frames.

    def mark_cell(self, cell, index):
        """Mark a cell and update `frame_box` and `num_changes`."""
        x, y = cell
        self.grid[x][y] = index

        if self.frame_box is not None:
            left, top, right, bottom = self.frame_box
            self.frame_box = (min(x, left), min(y, top), max(x, right),
                              max(y, bottom))
        else:
            self.frame_box = (x, y, x, y)

        self.num_changes += 1

    def encode_frame(self):
        if self.frame_box is not None:
            left, top, right, bottom = self.frame_box
        else:
            left, top, right, bottom = 0, 0, self.width - 1, self.height - 1

        width = right - left + 1
        height = bottom - top + 1
        descriptor = GIFWriter.image_descriptor(left * self.scale,
                                                top * self.scale,
                                                width * self.scale,
                                                height * self.scale)

        def get_frame_pixels():
            for i in range(width * height * self.scale * self.scale):
                y = i // (width * self.scale * self.scale)
                x = (i % (width * self.scale)) // self.scale
                val = self.grid[x + left][y + top]
                c = self.colormap[val]
                yield c

        frame = self.writer.LZW_encode(get_frame_pixels())
        self.num_changes = 0
        self.frame_box = None
        return descriptor + frame

    def paint_background(self, **kwargs):
        """Insert current frame at the beginning to use it as the background.
        This does not require the graphics control block."""
        if kwargs:
            self.set_colors(**kwargs)
        self.writer.data = self.encode_frame() + self.writer.data

    def output_frame(self):
        """Output current frame to the data stream. This method will not be directly called:
        it's called by `refresh_frame()` and `clear_remaining_changes()`."""
        control = self.writer.graphics_control_block(self.delay,
                                                     self.trans_index)
        self.writer.data += control + self.encode_frame()

    def refresh_frame(self):
        if self.num_changes >= self.speed:
            self.output_frame()

    def clear_remaining_changes(self):
        if self.num_changes > 0:
            self.output_frame()

    def set_colors(self, **kwargs):
        """`wc` is short for wall color, `tc` is short for tree color, etc."""
        color_dict = {'wc': 0, 'tc': 1, 'pc': 2, 'fc': 3}
        for key, val in kwargs.items():
            self.colormap[color_dict[key]] = val

    def pad_delay_frame(self, delay):
        self.writer.data += self.writer.pad_delay_frame(
            delay, self.trans_index)

    def write_to_gif(self, filename):
        self.writer.save_gif(filename)
Exemple #15
0
class Animation(object):
    '''
    This class is built on top of the 'Maze' class for encodig the algorithms into GIF images.
    It needs several further parameters to control the resulting image:

    1. scale: the size of the GIF image is (scale) x (size of the maze).
    2. loop: the number of loops of the GIF image.
    3. delay: the delay between two successive frames.
    4. trans_index: control which transparent color is used.
    5. init_dict: map the maze to an image (to communicate with our LZW encoder).
    6. speed: control how often a frame is rendered.
    '''
    def __init__(self, maze, scale=5, speed=30, loop=0):
        self.maze = maze
        self.writer = GIFWriter(maze.width * scale, maze.height * scale, loop)
        self.scale = scale
        self.speed = speed
        self.trans_index = 3
        self.delay = 5

        # this dict is used for communicating with our LZW encoder.
        # by modifying it we can color a maze in different ways.
        self.init_table = {str(c): c for c in range(4)}

    def set_transparent(self, index):
        self.trans_index = index

    def set_delay(self, delay):
        self.delay = delay

    def set_speed(self, speed):
        self.speed = speed

    def set_colors(self, **kwargs):
        colormap = {
            'wall_color': '0',
            'tree_color': '1',
            'path_color': '2',
            'fill_color': '3'
        }
        for key, val in kwargs.items():
            self.init_table[colormap[key]] = val

    def pad_delay_frame(self, delay):
        self.writer.data += self.writer.pad_delay_frame(
            delay, self.trans_index)

    def refresh_frame(self):
        if self.maze.num_changes >= self.speed:
            self.write_current_frame()

    def clear(self):
        '''output (possibly) remaining changes'''
        if self.maze.num_changes > 0:
            self.write_current_frame()

    def write_current_frame(self):
        control = self.writer.graphics_control_block(self.delay,
                                                     self.trans_index)
        self.writer.data += control + self.encode_frame()

    def paint_background(self, **kwargs):
        '''
        If no colors are specified then previous init_table will be used.
        This function allows you to insert current frame at the beginning of the file
        to serve as the background, it does not need the graphics control block.
        '''
        if kwargs:
            self.set_colors(**kwargs)

        self.writer.data = self.encode_frame() + self.writer.data

    def encode_frame(self):
        if self.maze.frame_box:
            left, top, right, bottom = self.maze.frame_box
        else:
            left, top, right, bottom = 0, 0, self.maze.width - 1, self.maze.height - 1

        width = right - left + 1
        height = bottom - top + 1
        descriptor = self.writer.image_descriptor(left * self.scale,
                                                  top * self.scale,
                                                  width * self.scale,
                                                  height * self.scale)

        input_data = [0] * width * height * self.scale * self.scale
        for i in range(len(input_data)):
            y = i // (width * self.scale * self.scale)
            x = (i % (width * self.scale)) // self.scale
            input_data[i] = self.maze.grid[x + left][y + top]

        self.maze.num_changes = 0
        self.maze.frame_box = None
        return descriptor + self.writer.LZW_encode(input_data, self.init_table)

    def write_to_gif(self, filename):
        self.writer.save(filename)