diff --git a/README.md b/README.md
index 863f5c2c08c929640f9e1fe67732966462b8d8d2..94b1d40619e459f9c6c25bb056adcd2c0fe7173d 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,8 @@ python cellular_automaton.py <optional arguments>
 |  `--parallel`   |                          | use multithreading                                                            |
 |  `--moore`      |                          | use moore neighborhood (*default Von Neumann*)                                |
 |  `--box`        |  from_x to_x from_y to_y | Rectangular box defined by 4 numbers, where agents will be distributed. (*default the whole room*) |
+|`--save` | | Save simulation to a movie|
+|`--maxframe` | MAX_FRAMES | Max frames of simulation (*default 1000*)|
                         
 ## Simulation results
 
diff --git a/cellular_automaton.py b/cellular_automaton.py
deleted file mode 100644
index 3dea2a7af1acf4d99eecd5b67fe270bcecb0a140..0000000000000000000000000000000000000000
--- a/cellular_automaton.py
+++ /dev/null
@@ -1,584 +0,0 @@
-#!/usr/bin/env python3
-
-from functools import lru_cache
-from multiprocessing.pool import Pool
-import itertools as it # for cartesian product
-import time
-import random
-import os
-import logging
-import argparse
-import numpy as np
-import matplotlib.pyplot as plt
-
-#######################################################
-MAX_STEPS = 1000
-steps = range(MAX_STEPS)
-
-cellSize = 0.4  # m
-vmax = 1.2
-dt = cellSize / vmax  # time step
-
-from_x, to_x = 1, 63  # todo parse this too
-from_y, to_y = 1, 63  # todo parse this too
-DEFAULT_BOX = [from_x, to_x, from_y, to_y]
-del from_x, to_x, from_y, to_y
-
-
-
-# DFF = np.ones( (dim_x, dim_y) ) # dynamic floor field
-#######################################################
-
-logfile = 'log.dat'
-logging.basicConfig(filename=logfile, level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
-
-
-def get_parser_args():
-    parser = argparse.ArgumentParser(
-        description='Cellular Automaton. Floor Field Model [Burstedde2001] Simulation of pedestrian'
-                    'dynamics using a two-dimensional cellular automaton Physica A, 295, 507-525, 2001')
-    parser.add_argument('-s', '--ks', type=float, default=2,
-                        help='sensitivity parameter for the  Static Floor Field (default 2)')
-    parser.add_argument('-d', '--kd', type=float, default=1,
-                        help='sensitivity parameter for the  Dynamic Floor Field (default 1)')
-    parser.add_argument('-n', '--numPeds', type=int, default=10, help='Number of agents (default 10)')
-    parser.add_argument('-p', '--plotS', action='store_const', const=True, default=False,
-                        help='plot Static Floor Field')
-    parser.add_argument('--plotD', action='store_const', const=True, default=False,
-                        help='plot Dynamic Floor Field')
-    parser.add_argument('--plotAvgD', action='store_const', const = True, default=False,
-                        help='plot average Dynamic Floor Field')
-    parser.add_argument('-P', '--plotP', action='store_const', const=True, default=False,
-                        help='plot Pedestrians')
-    parser.add_argument('-r', '--shuffle', action='store_const', const=True, default=True,
-                        help='random shuffle')
-    parser.add_argument('-v', '--reverse', action='store_const', const=True, default=False,
-                        help='reverse sequential update')
-    parser.add_argument('-l', '--log', type=argparse.FileType('w'), default='log.dat',
-                        help='log file (default log.dat)')
-    parser.add_argument('--decay', type=float, default=0.3,
-                        help='the decay probability of the Dynamic Floor Field (default 0.2')
-    parser.add_argument('--diffusion', type=float, default=0.1,
-                        help='the diffusion probability of the Dynamic Floor Field (default 0.2)')
-    parser.add_argument('-W', '--width', type=float, default=4.0,
-                        help='the width of the simulation area in meter, excluding walls')
-    parser.add_argument('-H', '--height', type=float, default=4.0,
-                        help='the height of the simulation room in meter, excluding walls')
-
-    parser.add_argument('-c', '--clean', action='store_const', const=True, default=False,
-                        help='remove files from directories dff/ sff/ and peds/')
-
-    parser.add_argument('-N', '--nruns', type=int, default=1,
-                        help='repeat the simulation N times')
-
-    parser.add_argument('--parallel', action='store_const', const=True, default=False,
-                        help='use multithreading')
-    parser.add_argument('--moore', action='store_const', const=True, default=False,
-                        help='use moore neighborhood. Default= Von Neumann')
-
-    parser.add_argument('--box', type=int, nargs=4, default=DEFAULT_BOX,
-                        help='Rectangular box, initially populated with agents: from_x, to_x, from_y, to_y. Default: The whole room')
-
-    _args = parser.parse_args()
-    return _args
-
-
-def init_obstacles():
-    return np.ones((dim_x, dim_y), int)  # obstacles/walls/boundaries
-
-
-def init_walls(exit_cells, ):
-    """
-    define where are the walls. Consider the exits
-    """
-    OBST = init_obstacles()
-
-    OBST[0, :] = OBST[-1, :] = OBST[:, -1] = OBST[:, 0] = -1
-    for e in exit_cells:
-        OBST[e] = 1
-    return OBST
-
-
-def check_N_pedestrians(_box, N_pedestrians):
-    """
-    check if <N_pedestrian> is too big. if so change it to fit in <box>
-    """
-    # holding box, where to distribute pedestrians
-    # ---------------------------------------------------
-    _from_x = _box[0]
-    _to_x = _box[1]
-    _from_y = _box[2]
-    _to_y  = _box[3]
-    # ---------------------------------------------------
-    nx = _to_x - _from_x + 1
-    ny = _to_y - _from_y + 1
-    if N_pedestrians > nx * ny:
-        logging.warning("N_pedestrians (%d) is too large (max. %d). Set to max." % (N_pedestrians, nx * ny))
-        N_pedestrians = nx * ny
-
-    return N_pedestrians
-
-
-def init_peds(N, box):
-    """
-    distribute N pedestrians in box
-    """
-    from_x, to_x = box[:2]
-    from_y, to_y = box[2:]
-    nx = to_x - from_x + 1
-    ny = to_y - from_y + 1
-    PEDS = np.ones(N, int)  # pedestrians
-    EMPTY_CELLS_in_BOX = np.zeros(nx * ny - N, int)  # the rest of cells in the box
-    PEDS = np.hstack((PEDS, EMPTY_CELLS_in_BOX))  # put 0s and 1s together
-    np.random.shuffle(PEDS)  # shuffle them
-    PEDS = PEDS.reshape((nx, ny))  # reshape to a box
-    EMPTY_CELLS = np.zeros((dim_x, dim_y), int)  # this is the simulation space
-    EMPTY_CELLS[from_x:to_x + 1, from_y:to_y + 1] = PEDS  # put in the box
-    logging.info("Init peds finished. Box: x: [%.2f, %.2f]. y: [%.2f, %.2f]",
-                 from_x, to_x, from_y, to_y)
-    return EMPTY_CELLS
-
-def plot_sff2(SFF, walls, i):
-    """
-    plots a numbered image. Useful for making movies
-    """
-    print("plot_sff: %.6d"%i)
-    fig = plt.figure()
-    ax = fig.add_subplot(111)
-    ax.cla()
-    plt.set_cmap('jet')
-    cmap = plt.get_cmap()
-    cmap.set_bad(color='k', alpha=0.8)
-    vect = SFF * walls
-    vect[vect < 0] = np.Inf
-#    print (vect)
-    max_value = np.max(SFF)
-    min_value = np.min(SFF)
-    plt.imshow(vect, cmap=cmap, interpolation='nearest', vmin=min_value, vmax=max_value, extent=[0, dim_y, 0, dim_x])  # lanczos nearest
-    plt.colorbar()
- #   print(i)
-    plt.title("%.6d"%i)
-    figure_name = os.path.join('sff', '%.6d.png'%i)
-    plt.savefig(figure_name)
-    plt.close()
-
-def plot_sff(SFF, walls):
-    fig = plt.figure()
-    ax = fig.add_subplot(111)
-    ax.cla()
-    plt.set_cmap('jet')
-    cmap = plt.get_cmap()
-    cmap.set_bad(color='k', alpha=0.8)
-    vect = SFF.copy()
-    vect[walls < 0] = np.Inf
-    max_value = np.max(SFF)
-    min_value = np.min(SFF)
-    plt.imshow(vect, cmap=cmap, interpolation='nearest', vmin=min_value, vmax=max_value, extent=[0, dim_y, 0, dim_x])  # lanczos nearest
-    plt.colorbar()
-    figure_name = os.path.join('sff', 'SFF.png')
-    plt.savefig(figure_name, dpi=600)
-    plt.close()
-
-def plot_dff(dff, walls, name="DFF", max_value=None, title=""):
-    fig = plt.figure()
-    ax = fig.add_subplot(111)
-    ax.cla()
-    plt.set_cmap('jet')    
-    cmap = plt.get_cmap()
-    cmap.set_bad(color='k', alpha=0.8)
-    vect =  dff.copy()
-    vect[walls < 0] = np.Inf
-    im = ax.imshow(vect, cmap=cmap, interpolation='nearest', vmin=0, vmax=max_value, extent=[0, dim_y, 0, dim_x])  # lanczos nearest
-    plt.colorbar(im, format='%.1f')
-    #cbar = plt.colorbar()
-    if title:
-        plt.title(title)
-
-    figure_name = os.path.join('dff', name+'.png')
-    plt.savefig(figure_name, dpi=600)
-    plt.close()
-    logging.info("plot dff. figure: {}.png".format(name))
-
-
-def plot_peds(peds, walls, i):
-    fig = plt.figure()
-    ax = fig.add_subplot(111)
-
-    ax.cla()
-    cmap = plt.get_cmap("gray")
-    cmap.set_bad(color='b', alpha=0.8)
-    N = np.sum(peds)
-    # print N, type(N)
-    # print peds+walls
-    #ax.axes.autoscale(False)
-    grid_x = np.arange(1, dim_x-1, cellSize)
-    grid_y = np.arange(1, dim_y-1, cellSize)
-
-    ax.imshow(peds + walls, cmap=cmap, interpolation='nearest', vmin=-1, vmax=2)  # 1-peds because I want the peds to be black
-    plt.grid(True, color='k', alpha=0.3)
-    plt.yticks(np.arange(1.5, peds.shape[0], 1))
-    plt.xticks(np.arange(1.5, peds.shape[1], 1))
-    plt.setp(ax.get_xticklabels(), visible=False)
-    plt.setp(ax.get_yticklabels(), visible=False)
-    ax.tick_params(axis='both', which='both', length=0)
-
-    S = 't: %3.3d  |  N: %3.3d ' % (i, N)
-    plt.title("%8s" % S)
-    figure_name = os.path.join('peds', 'peds%.6d.png' % i)
-    plt.savefig(figure_name)
-    plt.close()
-
-
-def init_DFF():
-    """
-    """
-    return np.zeros((dim_x, dim_y))
-
-
-def update_DFF(dff, diff):
-    #for cell in diff:
-    #    assert walls[cell] > -10
-    #     dff[cell] += 1
-
-    dff += diff
-
-    for i, j in it.chain(it.product(range(1, dim_x - 1), range(1, dim_y - 1)), exit_cells):
-        for _ in range(int(dff[i, j])):
-            if np.random.rand() < delta: # decay
-                dff[i, j] -= 1
-            elif np.random.rand() < alpha: # diffusion
-                dff[i, j] -= 1
-                dff[random.choice(get_neighbors((i, j)))] += 1
-        assert walls[i, j] > -10 or dff[i, j] == 0, (dff, i, j)
-    # dff[:] = np.ones((dim_x, dim_y))
-
-@lru_cache(1)
-def init_SFF(_exit_cells, _dim_x, _dim_y, drawS):
-    # start with exit's cells
-    SFF = np.empty((_dim_x, _dim_y))  # static floor field
-    SFF[:] = np.sqrt(_dim_x ** 2 + _dim_y ** 2)
-
-    make_videos = 0
-    if make_videos and drawS:
-        plot_sff2(SFF, walls, 1)
-
-    cells_initialised = []
-    for e in _exit_cells:
-        cells_initialised.append(e)
-        SFF[e] = 0
-
-    if make_videos and drawS:
-        plot_sff2(SFF, walls, 2)
-        i = 3
-    while cells_initialised:
-        cell = cells_initialised.pop(0)
-        neighbor_cells = get_neighbors(cell)
-        for neighbor in neighbor_cells:
-            # print ("cell",cell, "neighbor",neighbor)
-            if SFF[cell] + 1 < SFF[neighbor]:
-                SFF[neighbor] = SFF[cell] + 1
-                cells_initialised.append(neighbor)
-                # print(SFF)
-        # print(cells_initialised)
-        if make_videos and drawS:
-            plot_sff2(SFF, walls, i)
-            i += 1
-
-    return SFF
-
-@lru_cache(16*1024)
-def get_neighbors(cell):
-    """
-     von Neumann neighborhood
-    """
-    neighbors = []
-    i, j = cell
-    if i < dim_x - 1 and walls[(i + 1, j)] >= 0:
-        neighbors.append((i + 1, j))
-    if i >= 1 and walls[(i - 1, j)] >= 0:
-        neighbors.append((i - 1, j))
-    if j < dim_y - 1 and walls[(i, j + 1)] >= 0:
-        neighbors.append((i, j + 1))
-    if j >= 1 and walls[(i, j - 1)] >= 0:
-        neighbors.append((i, j - 1))
-
-    # moore
-    if moore:
-        if i >= 1 and j >= 1 and walls[(i-1, j - 1)] >= 0:
-            neighbors.append((i-1, j - 1))
-        if i < dim_x - 1 and  j < dim_y -1  and walls[(i+1, j+1)] >= 0:
-            neighbors.append((i+1, j + 1))
-        if i < dim_x - 1 and  j >= 1  and walls[(i+1, j-1)] >= 0:
-            neighbors.append((i+1, j - 1))
-        if i >= 1 and  j < dim_y -1  and walls[(i-1, j+1)] >= 0:
-            neighbors.append((i-1, j + 1))
-
-
-    # not shuffling singnificantly alters the simulation...
-    random.shuffle(neighbors)
-    return neighbors
-
-
-def seq_update_cells(peds, sff, dff, kappaD, kappaS, shuffle, reverse):
-    """
-    sequential update
-    input
-       - peds:
-       - sff:
-       - dff:
-       - prob_walls:
-       - kappaD:
-       - kappaS:
-       - rand: random shuffle
-    return
-       - new peds
-    """
-
-    tmp_peds = np.empty_like(peds)  # temporary cells
-    np.copyto(tmp_peds, peds)
-
-    dff_diff = np.zeros((dim_x, dim_y))
-
-    grid = list(it.product(range(1, dim_x - 1), range(1, dim_y - 1))) + list(exit_cells)
-    if shuffle:  # sequential random update
-        random.shuffle(grid)
-    elif reverse:  # reversed sequential update
-        grid.reverse()
-
-    for (i, j) in grid:  # walk through all cells in geometry
-        if peds[i, j] == 0:
-            continue
-
-        if (i, j) in exit_cells:
-            tmp_peds[i, j] = 0
-            dff_diff[i, j] += 1
-            continue
-
-        p = 0
-        probs = {}
-        cell = (i, j)
-        for neighbor in get_neighbors(cell):  # get the sum of probabilities
-            # original code:
-            # probability = np.exp(-kappaS * sff[neighbor]) * np.exp(kappaD * dff[neighbor]) * \
-            # (1 - tmp_peds[neighbor])
-            # the absolute value of the exponents can get very large yielding 0 or
-            # inifite probability.
-            # to prevent this we multiply every probability with exp(kappaS * sff[cell) and
-            # exp(-kappaD * dff[cell]).
-            # since the probabilities are normalized this doesn't have any effect on the model
-
-            probability = np.exp(kappaS * (sff[cell] - sff[neighbor])) * \
-                          np.exp(kappaD * (dff[neighbor] - dff[cell])) * \
-                          (1 - tmp_peds[neighbor])
-
-            p += probability
-            probs[neighbor] = probability
-
-        if p == 0:  # pedestrian in cell can not move
-            continue
-
-        r = np.random.rand() * p
-        # print ("start update")
-        for neighbor in get_neighbors(cell): #TODO: shuffle?
-            r -= probs[neighbor]
-            if r <= 0:  # move to neighbor cell
-                tmp_peds[neighbor] = 1
-                tmp_peds[i, j] = 0
-                dff_diff[i, j] += 1
-                break
-
-    return tmp_peds, dff_diff
-
-
-def print_logs(N_pedestrians, width, height, t, dt, nruns, Dt):
-    """
-    print some infos to the screen
-    """
-    print ("Simulation of %d pedestrians" % N_pedestrians)
-    print ("Simulation space (%.2f x %.2f) m^2" % (width, height))
-    print ("SFF:  %.2f | DFF: %.2f" % (kappaS, kappaD))
-    print ("Mean Evacuation time: %.2f s, runs: %d" % (t * dt / nruns, nruns))
-    print ("Total Run time: %.2f s" % Dt)
-    print ("Factor: x%.2f" % (dt * t / Dt))
-
-
-def setup_dir(dir, clean):
-    print("make ", dir)
-    if os.path.exists(dir) and clean:
-        os.system('rm -rf %s' % dir)
-    os.makedirs(dir, exist_ok=True)
-
-
-def simulate(args):
-
-    n, npeds, box, sff, shuffle, reverse, drawP, giveD = args
-    print("init %d agents in box=[%d, %d, %d, %d]"%(npeds, box[0], box[1], box[2], box[3]))
-    peds = init_peds(npeds, box)
-    dff = init_DFF()
-
-    old_dffs = []
-    for t in steps:  # simulation loop
-        print('\tn: %3d ----  t: %3d |  N: %3d' % (n, t, int(np.sum(peds))))
-        if drawP:
-            plot_peds(peds, walls, t)
-
-        peds, dff_diff = seq_update_cells(peds, sff, dff, kappaD, kappaS,
-                                          shuffle, reverse)
-
-        update_DFF(dff, dff_diff)
-        if giveD:
-            old_dffs.append((t, dff.copy()))
-
-        if not peds.any(): # is everybody out? TODO: check this. Some bug is lurking here
-            print("Quite simulation")
-            break
-    # else:
-    #     raise TimeoutError("simulation taking too long")
-
-    if giveD:
-        return t, old_dffs
-    else:
-        return t
-
-def check_box(box):
-    """
-    exit if box is not well defined
-    """
-    assert (box[0] < box[1]), "from_x smaller than to_x"
-    assert (box[2] < box[3]), "from_y smaller than to_y"
-
-
-
-def main(args):
-    global kappaS, kappaD, dim_y, dim_x, exit_cells, SFF, alpha, delta, walls, parallel, box, moore
-    # init parameters
-    drawS = args.plotS  # plot or not
-    drawP = args.plotP  # plot or not
-    kappaS = args.ks
-    kappaD = args.kd
-    npeds = args.numPeds
-    shuffle = args.shuffle
-    reverse = args.reverse
-    drawD = args.plotD
-    drawD_avg = args.plotAvgD
-    clean_dirs = args.clean
-    width = args.width  # in meters
-    height = args.height  # in meters
-    parallel = args.parallel
-    box = args.box
-    check_box(box)
-    moore = args.moore
-    # check if no box is specified
-    if moore:
-        print("Neighborhood: Moore")
-    else:
-        print("Neighborhood: Von Neumann")
-
-
-    if parallel and drawP :
-        raise NotImplementedError("cannot plot pedestrians when multiprocessing")
-
-
-    # TODO check if width and hight are multiples of cellSize
-    dim_y = int(width / cellSize + 2 + 0.00000001)  # number of columns, add ghost cells
-    dim_x = int(height / cellSize + 2 + 0.00000001)  # number of rows, add ghost cells
-    print("cellsize: ", cellSize, " dim_x: ", dim_x, " dim_y: ", dim_y)
-    if box == DEFAULT_BOX:
-        print("box == room")
-        box = [1, dim_x - 2, 1, dim_y - 2]
-
-
-    nruns = args.nruns
-
-    exit_cells = frozenset(((dim_x // 2, dim_y - 1), (dim_x // 2 + 1, dim_y - 1),
-                            (dim_x - 1, dim_y//2 + 1) , (dim_x - 1, dim_y//2),
-                            (0, dim_y//2 + 1) , (1, dim_y//2),
-                            (dim_x//2 + 1, 0) , (dim_x//2, 0)
-    ))
-
-    delta = args.decay
-    alpha = args.diffusion
-
-    npeds = check_N_pedestrians(box, npeds)
-
-    walls = init_walls(exit_cells)
-
-    sff = init_SFF(exit_cells, dim_x, dim_y, drawS)
-    init_obstacles()
-    if drawS:
-        setup_dir('sff', clean_dirs)
-        plot_sff(sff, walls)
-
-
-
-    t1 = time.time()
-    tsim = 0
-
-    if drawP: setup_dir('peds', clean_dirs)
-    if drawD or drawD_avg: setup_dir('dff', clean_dirs)
-
-    times = []
-    old_dffs = []
-
-    if not parallel:
-        for n in range(nruns):
-            print("n= ", n, " nruns=", nruns)
-            if drawD_avg or drawD:
-                t, dffs = simulate((n, npeds, box, sff, shuffle, reverse,
-                                    drawP, drawD_avg or drawD))
-                old_dffs += dffs
-            else:
-                t = simulate((n, npeds, box, sff, shuffle, reverse, drawP,
-                              drawD_avg or drawD))
-            tsim += t
-            print("time ", tsim)
-            times.append(t * dt)
-        if moore:
-            print("save moore.npy")
-            np.save("moore.npy",times)
-        else:
-            print("save neumann.npy")
-            np.save("neumann.npy",times)
-    else:
-
-        nproc = min(nruns, 8)
-        print('using {} processes'.format(nproc))
-        jobs = [(n, npeds, box, sff, shuffle, reverse, drawP, drawD_avg or drawD)
-                for n in range(nruns)]
-
-        with Pool(nproc) as pool:
-            results = pool.map(simulate, jobs)
-
-
-        if drawD_avg or drawD:
-            ts, chunked_dffs = zip(*results)
-            times = [t * dt for t in ts]
-            tsim = sum(ts)
-            old_dffs = sum(chunked_dffs, [])
-        else:
-            times = [t * dt for t in results]
-            tsim = sum(results)
-
-
-    t2 = time.time()
-    print_logs(npeds, width, height, tsim, dt, nruns, t2 - t1)
-    if drawD_avg:
-        print('plotting average DFF')
-        if moore:
-            title = "DFF-avg_Moore_runs_%d_N%d_S%.2f_D%.2f"%(nruns, npeds, kappaS, kappaD)
-        else:
-            title = "DFF-avg_Neumann_runs_%d_N%d_S%.2f_D%.2f"%(nruns, npeds, kappaS, kappaD)
-        plot_dff(sum(x[1] for x in old_dffs) / tsim, walls, title)
-# title=r"$t = {:.2f}$ s, N={}, #runs = {}, $\kappa_S={}\;, \kappa_D={}$".format(sum(times), npeds, nruns, kappaS, kappaD)
-    if drawD:
-        print('plotting DFFs...')
-        max_dff = max(field.max() for _, field in old_dffs)
-        for tm, dff in old_dffs:
-            print("t: %3.4d" % tm)
-            plot_dff(dff, walls, "DFF-%3.4d"%tm, max_dff, "t: %3.4d" % tm)
-
-    return times
-
-if __name__ == "__main__":
-    args = get_parser_args()
-    main(args)
diff --git a/main.py b/main.py
index d52555d9728b2dc4f151964954c6c057b94e721b..db41cb8ea6c4c61956260683ae488f63e94555cb 100644
--- a/main.py
+++ b/main.py
@@ -46,7 +46,7 @@ def get_parser_args():
     parser.add_argument('--diffusion', type=float, default=0.1,
                         help='the diffusion probability of the Dynamic Floor Field (default 0.2)')
     parser.add_argument('--maxframe', type=int, default=1000,
-                        help='Max simulation frame  (default 1000')
+                        help='Max simulation frame  (default 1000)')
 
     parser.add_argument('-W', '--width', type=float, default=4.0,
                         help='the width of the simulation area in meter, excluding walls')