from collections import namedtuple
from random import random
from pprint import pprint as pp
 
Grid = namedtuple('Grid', 'cell, hwall, vwall')
 
M, N, t = 10, 10, 1000
 
class PercolatedException(Exception): pass
 
HVF = [(' .', ' _'), (':', '|'), (' ', '#')]  # Horiz, vert, fill chars
 
def newgrid(p):
    hwall = [[int(random() < p) for m in range(M)] 
             for n in range(N+1)]
    vwall = [[(1 if m in (0, M) else int(random() < p)) for m in range(M+1)] 
             for n in range(N)]
    cell = [[0 for m in range(M)] 
             for n in range(N)]
    return Grid(cell, hwall, vwall)
 
def pgrid(grid, percolated=None):
    cell, hwall, vwall = grid
    h, v, f = HVF
    for n in range(N):
        print('    ' + ''.join(h[hwall[n][m]] for m in range(M)))
        print('%i)  ' % (n % 10) + ''.join(v[vwall[n][m]] + f[cell[n][m] if m < M else 0]
                                          for m in range(M+1))[:-1])
    n = N
    print('    ' + ''.join(h[hwall[n][m]] for m in range(M)))
    if percolated: 
        where = percolated.args[0][0]
        print('!)  ' + '  ' * where + ' ' + f[1])
 
def pour_on_top(grid):
    cell, hwall, vwall = grid
    n = 0
    try:
        for m in range(M):
            if not hwall[n][m]:
                flood_fill(m, n, cell, hwall, vwall)
    except PercolatedException as ex:
        return ex
    return None
 
 
def flood_fill(m, n, cell, hwall, vwall):
    # fill cell 
    cell[n][m] = 1
    # bottom
    if n < N - 1 and not hwall[n + 1][m] and not cell[n+1][m]:
        flood_fill(m, n+1, cell, hwall, vwall)
    # THE bottom
    elif n == N - 1 and not hwall[n + 1][m]:
        raise PercolatedException((m, n+1))
    # left
    if m and not vwall[n][m] and not cell[n][m - 1]:
        flood_fill(m-1, n, cell, hwall, vwall)
    # right
    if m < M - 1 and not vwall[n][m + 1] and not cell[n][m + 1]:
        flood_fill(m+1, n, cell, hwall, vwall)
    # top
    if n and not hwall[n][m] and not cell[n-1][m]:
        flood_fill(m, n-1, cell, hwall, vwall)
 
if __name__ == '__main__':
    sample_printed = False
    pcount = {}
    for p10 in range(11):
        p = (10 - p10) / 10.0    # count down so sample print is interesting
        pcount[p] = 0
        for tries in range(t):
            grid = newgrid(p)
            percolated = pour_on_top(grid)
            if percolated:
                pcount[p] += 1
                if not sample_printed:
                    print('\nSample percolating %i x %i grid' % (M, N))
                    pgrid(grid, percolated)
                    sample_printed = True
    print('\n p: Fraction of %i tries that percolate through' % t )
 
    pp({p:c/float(t) for p, c in pcount.items()})