import random

def makeModel(data):
    # Infection rate - how likely is it that a
    # human becomes a zombie after an encounter?
    data["rate"] = 0.5
    # Size of the grid
    data["size"] = 20
    # Humans & zombies are similar, store them in the same place
    data["creatures"] = []
    for human in range(20):
        # A creature has a position and species
        tmp = { "row" : random.randint(0, data["size"]-1),
                "col" : random.randint(0, data["size"]-1),
                "species" : "human" }
        data["creatures"].append(tmp)
    for zombie in range(5):
        tmp = { "row" : random.randint(0, data["size"]-1),
                "col" : random.randint(0, data["size"]-1),
                "species" : "zombie" }
        data["creatures"].append(tmp)

def makeView(data, canvas):
    # Draw an underlying grid
    cellSize = 400 / data["size"] # 400 is the window size
    for row in range(data["size"]):
        for col in range(data["size"]):
            canvas.create_rectangle(col*cellSize, row*cellSize,
                                    (col+1)*cellSize, (row+1)*cellSize)

    # Draw creatures on top of the grid
    for creature in data["creatures"]:
        row = creature["row"]
        col = creature["col"]
        if creature["species"] == "human":
            color = "green"
        else:
            color = "purple"
        canvas.create_rectangle(col*cellSize, row*cellSize,
                                (col+1)*cellSize, (row+1)*cellSize,
                                fill=color)

def runRules(data, call):
    # When everyone is a zombie, print the number of days
    # that have passed and end the simulation
    if allZombies(data["creatures"]):
        print(call)
        exit()

    # Move the zombies in random directions
    zombies = []
    for creature in data["creatures"]:
        if creature["species"] == "zombie":
            zombies.append(creature)
            moves = [ [-1, 0], [1, 0], [0, -1], [0, 1] ]
            move = random.choice(moves)
            creature["row"] += move[0]
            creature["col"] += move[1]
            # If a zombie moves offscreen, force them back in
            if not onscreen(creature, data["size"]):
                creature["row"] -= move[0]
                creature["col"] -= move[1]

    # If a human is bordering a zombie, check if they should turn
    for creature in data["creatures"]:
        if creature["species"] == "human":
            for zombie in zombies:
                if bordering(creature["row"], creature["col"], zombie["row"], zombie["col"]):
                    odds = random.random()
                    if odds < data["rate"]:
                        creature["species"] = "zombie"



# Need to be within both the width and the height
def onscreen(creature, size):
    return 0 <= creature["row"] < size and \
           0 <= creature["col"] < size

# If in the same row and at most one apart, you're bordering
def bordering(row1, col1, row2, col2):
    if row1 == row2 and abs(col1 - col2) <= 1:
        return True
    elif col1 == col2 and abs(row1 - row2) <= 1:
        return True
    else:
        return False

# Are all of the creatures zombies yet?
def allZombies(creatures):
    for creature in creatures:
        if creature["species"] == "human":
            return False # any humans? not done yet
    return True

def keyPressed(data, event):
    pass

def mousePressed(data, event):
    pass

# You do not need to be able to write the following functions;
# just modify the five functions above.

from tkinter import *

def timeLoop(data, canvas, call):
    runRules(data, call)

    canvas.delete(ALL)
    makeView(data, canvas)
    canvas.update()

    canvas.after(data["timeRate"], timeLoop, data, canvas, call + 1)

def keyEventHandler(data, canvas, event):
    keyPressed(data, event)

    canvas.delete(ALL)
    makeView(data, canvas)
    canvas.update()

def mouseEventHandler(data, canvas, event):
    mousePressed(data, event)

    canvas.delete(ALL)
    makeView(data, canvas)
    canvas.update()

def runSimulation(w, h, timeRate):
    data = { }
    data["timeRate"] = int(timeRate * 1000) # call will be in ms
    makeModel(data)

    root = Tk()
    canvas = Canvas(root, width=w, height=h)
    canvas.configure(bd=0, highlightthickness=0)
    canvas.pack()
    makeView(data, canvas)

    canvas.after(data["timeRate"], timeLoop, data, canvas, 1)

    root.bind("<Key>", lambda event : keyEventHandler(data, canvas, event))
    root.bind("<Button-1>", lambda event : mouseEventHandler(data, canvas, event))

    root.mainloop()

runSimulation(400, 400, 0.1)