#!/usr/bin/env python3
# To run simply type python3 GOL.py in a terminal
from tkinter import *
from tkinter import messagebox, simpledialog

#to be changed later by the user, with default values from the original game
REVIVES = (3,)
SURVIVES = (2,3)

#directions which we will move the board
LEFT = "Left"
RIGHT = "Right"
UP = "Up"
DOWN = "Down"

#placement for moving the board
PLACEMENT = {UP: (0, 1), DOWN: (0, -1), LEFT: (1, 0), RIGHT: (-1, 0)}

#planned for zooming, not used
SIZE = 10

#coords of figures to be colored
#FIGURENUM means a frame of an oscillation of given figure
#NOT_FIGURENUM means the dead points in a rectangle surrounding the FIGURENUM
#only DAEMONS don't have NOT_DAEMONS, because i'm pretty sure if we have found one :)
TOAD1 = [(0, 0), (1, -1), (1, 2), (2, -1), (2, 2), (3, 1)]

TOAD2 = [(0, 0), (0, 1), (0, 2), (1, 1), (1, 2), (1, 3)]

TOAD3 = [(0, 0), (1, -2), (1, 1), (2, -2), (2, 1), (3, -1)]

TOAD4 = [(0, 0), (0, 1), (0, 2), (1, -1), (1, 0), (1, 1)]

TOAD5 = [(0, 0), (0, 1), (1, -1), (2, 2), (3, 0), (3, 1)]

TOAD6 = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1), (3, 1)]

TOAD7 = [(0, 0), (0, 1), (1, 2), (2, -1), (3, 0), (3, 1)]

TOAD8 = [(0, 0), (1, -1), (1, 0), (2, -1), (2, 0), (3, -1)]

NOT_TOAD1 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 1), (0, 2), (0, 3), (1, -2), (1, 0), (1, 1), (1, 3), (2, -2), (2, 0), (2, 1), (2, 3), (3, -2), (3, -1), (3, 0), (3, 2), (3, 3), (4, -2), (4, -1), (4, 0), (4, 1), (4, 2), (4, 3)]

NOT_TOAD2 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (-1, 4), (0, -1), (0, 3), (0, 4), (1, -1), (1, 0), (1, 4), (2, -1), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4)]

NOT_TOAD3 = [(-1, -3), (-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -3), (0, -2), (0, -1), (0, 1), (0, 2), (1, -3), (1, -1), (1, 0), (1, 2), (2, -3), (2, -1), (2, 0), (2, 2), (3, -3), (3, -2), (3, 0), (3, 1), (3, 2), (4, -3), (4, -2), (4, -1), (4, 0), (4, 1), (4, 2)]

NOT_TOAD4 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 3), (1, -2), (1, 2), (1, 3), (2, -2), (2, -1), (2, 0), (2, 1), (2, 2), (2, 3)]

NOT_TOAD5 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 2), (0, 3), (1, -2), (1, 0), (1, 1), (1, 2), (1, 3), (2, -2), (2, -1), (2, 0), (2, 1), (2, 3), (3, -2), (3, -1), (3, 2), (3, 3), (4, -2), (4, -1), (4, 0), (4, 1), (4, 2), (4, 3)]

NOT_TOAD6 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -1), (0, 1), (0, 2), (1, -1), (1, 2), (2, -1), (2, 2), (3, -1), (3, 0), (3, 2), (4, -1), (4, 0), (4, 1), (4, 2)]

NOT_TOAD7 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 2), (0, 3), (1, -2), (1, -1), (1, 0), (1, 1), (1, 3), (2, -2), (2, 0), (2, 1), (2, 2), (2, 3), (3, -2), (3, -1), (3, 2), (3, 3), (4, -2), (4, -1), (4, 0), (4, 1), (4, 2), (4, 3)]

NOT_TOAD8 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (0, -2), (0, -1), (0, 1), (1, -2), (1, 1), (2, -2), (2, 1), (3, -2), (3, 0), (3, 1), (4, -2), (4, -1), (4, 0), (4, 1)]

GLIDERDL1 = [(0, 0), (0, 1), (1, 1), (1, 2), (2, 0)]

GLIDERDL2 = [(0, 0), (0, 1), (0, 2), (1, 2), (2, 1)]

GLIDERDL3 = [(0, 0), (1, 0), (1, 1), (2, -1), (2, 1)]

GLIDERDL4 = [(0, 0), (0, 1), (1, -1), (1, 1), (2, 1)]

NOT_GLIDERDL1 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 2), (0, 3), (1, -1), (1, 0), (1, 3), (2, -1), (2, 1), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

NOT_GLIDERDL2 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 3), (1, -1), (1, 0), (1, 1), (1, 3), (2, -1), (2, 0), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

NOT_GLIDERDL3 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 1), (0, 2), (1, -2), (1, -1), (1, 2), (2, -2), (2, 0), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

NOT_GLIDERDL4 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 2), (1, -2), (1, 0), (1, 2), (2, -2), (2, -1), (2, 0), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

GLIDERDR1 = [(0, 0), (1, 1), (1, 2), (2, 0), (2, 1)]

GLIDERDR2 = [(0, 0), (1, 1), (2, -1), (2, 0), (2, 1)]

GLIDERDR3 = [(0, 0), (0, 2), (1, 1), (1, 2), (2, 1)]

GLIDERDR4 = [(0, 0), (1, -2), (1, 0), (2, -1), (2, 0)]

NOT_GLIDERDR1 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 1), (0, 2), (0, 3), (1, -1), (1, 0), (1, 3), (2, -1), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

NOT_GLIDERDR2 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 1), (0, 2), (1, -2), (1, -1), (1, 0), (1, 2), (2, -2), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

NOT_GLIDERDR3 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 1), (0, 3), (1, -1), (1, 0), (1, 3), (2, -1), (2, 0), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

NOT_GLIDERDR4 = [(-1, -3), (-1, -2), (-1, -1), (-1, 0), (-1, 1), (0, -3), (0, -2), (0, -1), (0, 1), (1, -3), (1, -1), (1, 1), (2, -3), (2, -2), (2, 1), (3, -3), (3, -2), (3, -1), (3, 0), (3, 1)]

GLIDERUL1 = [(0, 0), (0, 1), (1, -1), (1, 0), (2, 1)]

GLIDERUL2 = [(0, 0), (0, 1), (0, 2), (1, 0), (2, 1)]

GLIDERUL3 = [(0, 0), (1, -1), (1, 0), (2, -1), (2, 1)]

GLIDERUL4 = [(0, 0), (0, 1), (1, 0), (1, 2), (2, 0)]

NOT_GLIDERUL1 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 2), (1, -2), (1, 1), (1, 2), (2, -2), (2, -1), (2, 0), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

NOT_GLIDERUL2 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 3), (1, -1), (1, 1), (1, 2), (1, 3), (2, -1), (2, 0), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

NOT_GLIDERUL3 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 1), (0, 2), (1, -2), (1, 1), (1, 2), (2, -2), (2, 0), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

NOT_GLIDERUL4 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 2), (0, 3), (1, -1), (1, 1), (1, 3), (2, -1), (2, 1), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

GLIDERUR1 = [(0, 0), (1, -2), (1, -1), (2, -1), (2, 0)]

GLIDERUR2 = [(0, 0), (1, -1), (2, -1), (2, 0), (2, 1)]

GLIDERUR3 = [(0, 0), (0, 2), (1, 0), (1, 1), (2, 1)]

GLIDERUR4 = [(0, 0), (1, 0), (1, 2), (2, 0), (2, 1)]

NOT_GLIDERUR1 = [(-1, -3), (-1, -2), (-1, -1), (-1, 0), (-1, 1), (0, -3), (0, -2), (0, -1), (0, 1), (1, -3), (1, 0), (1, 1), (2, -3), (2, -2), (2, 1), (3, -3), (3, -2), (3, -1), (3, 0), (3, 1)]

NOT_GLIDERUR2 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 1), (0, 2), (1, -2), (1, 0), (1, 1), (1, 2), (2, -2), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

NOT_GLIDERUR3 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 1), (0, 3), (1, -1), (1, 2), (1, 3), (2, -1), (2, 0), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

NOT_GLIDERUR4 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 1), (0, 2), (0, 3), (1, -1), (1, 1), (1, 3), (2, -1), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

LWSSL1 = [(0, 0), (1, -1), (1, 0), (1, 1), (2, -1), (2, 1), (2, 2), (3, 0), (3, 1), (3, 2), (4, 0), (4, 1)]

LWSSL2 = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 3), (2, 0), (3, 0), (4, 1), (4, 3)]

LWSSL3 = [(0, 0), (1, -1), (1, 0), (1, 1), (2, -2), (2, -1), (2, 1), (3, -2), (3, -1), (3, 0), (4, -1), (4, 0)]

LWSSL4 = [(0, 0), (0, 1), (0, 2), (1, -1), (1, 2), (2, 2), (3, 2), (4, -1), (4, 1)]

NOT_LWSSL1 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 1), (0, 2), (0, 3), (1, -2), (1, 2), (1, 3), (2, -2), (2, 0), (2, 3), (3, -2), (3, -1), (3, 3), (4, -2), (4, -1), (4, 2), (4, 3), (5, -2), (5, -1), (5, 0), (5, 1), (5, 2), (5, 3)]

NOT_LWSSL2 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (-1, 4), (0, -1), (0, 3), (0, 4), (1, -1), (1, 1), (1, 2), (1, 4), (2, -1), (2, 1), (2, 2), (2, 3), (2, 4), (3, -1), (3, 1), (3, 2), (3, 3), (3, 4), (4, -1), (4, 0), (4, 2), (4, 4), (5, -1), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4)]

NOT_LWSSL3 = [(-1, -3), (-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -3), (0, -2), (0, -1), (0, 1), (0, 2), (1, -3), (1, -2), (1, 2), (2, -3), (2, 0), (2, 2), (3, -3), (3, 1), (3, 2), (4, -3), (4, -2), (4, 1), (4, 2), (5, -3), (5, -2), (5, -1), (5, 0), (5, 1), (5, 2)]

NOT_LWSSL4 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 3), (1, -2), (1, 0), (1, 1), (1, 3), (2, -2), (2, -1), (2, 0), (2, 1), (2, 3), (3, -2), (3, -1), (3, 0), (3, 1), (3, 3), (4, -2), (4, 0), (4, 2), (4, 3), (5, -2), (5, -1), (5, 0), (5, 1), (5, 2), (5, 3)]

LWSSR1 = [(0, 0), (0, 1), (1, -1), (1, 0), (1, 1), (2, -1), (2, 0), (2, 2), (3, 0), (3, 1), (3, 2), (4, 1)]

LWSSR2 = [(0, 0), (0, 2), (1, 3), (2, 3), (3, 0), (3, 3), (4, 1), (4, 2), (4, 3)]

LWSSR3 = [(0, 0), (0, 1), (1, 0), (1, 1), (1, 2), (2, -1), (2, 1), (2, 2), (3, -1), (3, 0), (3, 1), (4, 0)]

LWSSR4 = [(0, 0), (0, 2), (1, -1), (2, -1), (3, -1), (3, 2), (4, -1), (4, 0), (4, 1)]

NOT_LWSSR1 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 2), (0, 3), (1, -2), (1, 2), (1, 3), (2, -2), (2, 1), (2, 3), (3, -2), (3, -1), (3, 3), (4, -2), (4, -1), (4, 0), (4, 2), (4, 3), (5, -2), (5, -1), (5, 0), (5, 1), (5, 2), (5, 3)]

NOT_LWSSR2 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (-1, 4), (0, -1), (0, 1), (0, 3), (0, 4), (1, -1), (1, 0), (1, 1), (1, 2), (1, 4), (2, -1), (2, 0), (2, 1), (2, 2), (2, 4), (3, -1), (3, 1), (3, 2), (3, 4), (4, -1), (4, 0), (4, 4), (5, -1), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4)]

NOT_LWSSR3 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 2), (0, 3), (1, -2), (1, -1), (1, 3), (2, -2), (2, 0), (2, 3), (3, -2), (3, 2), (3, 3), (4, -2), (4, -1), (4, 1), (4, 2), (4, 3), (5, -2), (5, -1), (5, 0), (5, 1), (5, 2), (5, 3)]

NOT_LWSSR4 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -2), (0, -1), (0, 1), (0, 3), (1, -2), (1, 0), (1, 1), (1, 2), (1, 3), (2, -2), (2, 0), (2, 1), (2, 2), (2, 3), (3, -2), (3, 0), (3, 1), (3, 3), (4, -2), (4, 2), (4, 3), (5, -2), (5, -1), (5, 0), (5, 1), (5, 2), (5, 3)]

BLOCK = [(0,0), (0,1), (1,0), (1,1)]

NOT_BLOCK = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -1), (0, 2), (1, -1), (1, 2), (2, -1), (2, 0), (2, 1), (2, 2)]

BLINKER1 = [(0, 0), (0, 1), (0, 2)]

BLINKER2 = [(0, 0), (1, 0), (2, 0)]

NOT_BLINKER1 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 3), (1, -1), (1, 0), (1, 1), (1, 2), (1, 3)]

NOT_BLINKER2 = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 1), (2, -1), (2, 1), (3, -1), (3, 0), (3, 1)]

BOAT1 = [(0, 0), (1, -1), (1, 1), (2, 0), (2, 1)]

BOAT2 = [(0, 0), (1, -1), (1, 1), (2, -1), (2, 0)]

BOAT3 = [(0, 0), (0, 1), (1, 0), (1, 2), (2, 1)]

BOAT4 = [(0, 0), (0, 1), (1, -1), (1, 1), (2, 0)]

NOT_BOAT1 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 1), (0, 2), (1, -2), (1, 0), (1, 2), (2, -2), (2, -1), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

NOT_BOAT2 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 1), (0, 2), (1, -2), (1, 0), (1, 2), (2, -2), (2, 1), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

NOT_BOAT3 = [(-1, -1), (-1, 0), (-1, 1), (-1, 2), (-1, 3), (0, -1), (0, 2), (0, 3), (1, -1), (1, 1), (1, 3), (2, -1), (2, 0), (2, 2), (2, 3), (3, -1), (3, 0), (3, 1), (3, 2), (3, 3)]

NOT_BOAT4 = [(-1, -2), (-1, -1), (-1, 0), (-1, 1), (-1, 2), (0, -2), (0, -1), (0, 2), (1, -2), (1, 0), (1, 2), (2, -2), (2, -1), (2, 1), (2, 2), (3, -2), (3, -1), (3, 0), (3, 1), (3, 2)]

DAEMONS1 = [(0, 0), (0, 1), (0, 7), (0, 8), (1, 1), (1, 2), (1, 6), (1, 7), (2, -2), (2, 1), (2, 3), (2, 5), (2, 7), (2, 10), (3, -2), (3, -1), (3, 0), (3, 2), (3, 3), (3, 5), (3, 6), (3, 8), (3, 9), (3, 10), (4, -1), (4, 1), (4, 3), (4, 5), (4, 7), (4, 9), (5, 0), (5, 1), (5, 2), (5, 6), (5, 7), (5, 8), (7, 0), (7, 1), (7, 2), (7, 6), (7, 7), (7, 8), (8, -1), (8, 1), (8, 3), (8, 5), (8, 7), (8, 9), (9, -2), (9, -1), (9, 0), (9, 2), (9, 3), (9, 5), (9, 6), (9, 8), (9, 9), (9, 10), (10, -2), (10, 1), (10, 3), (10, 5), (10, 7), (10, 10), (11, 1), (11, 2), (11, 6), (11, 7), (12, 0), (12, 1), (12, 7), (12, 8)]

DAEMONS2 = [(0, 0), (0, 6), (1, 0), (1, 6), (2, 0), (2, 1), (2, 5), (2, 6), (4, -4), (4, -3), (4, -2), (4, 1), (4, 2), (4, 4), (4, 5), (4, 8), (4, 9), (4, 10), (5, -2), (5, 0), (5, 2), (5, 4), (5, 6), (5, 8), (6, 0), (6, 1), (6, 5), (6, 6), (8, 0), (8, 1), (8, 5), (8, 6), (9, -2), (9, 0), (9, 2), (9, 4), (9, 6), (9, 8), (10, -4), (10, -3), (10, -2), (10, 1), (10, 2), (10, 4), (10, 5), (10, 8), (10, 9), (10, 10), (12, 0), (12, 1), (12, 5), (12, 6), (13, 0), (13, 6), (14, 0), (14, 6)]

DAEMONS3 = [(0, 0), (0, 1), (0, 2), (0, 6), (0, 7), (0, 8), (2, -2), (2, 3), (2, 5), (2, 10), (3, -2), (3, 3), (3, 5), (3, 10), (4, -2), (4, 3), (4, 5), (4, 10), (5, 0), (5, 1), (5, 2), (5, 6), (5, 7), (5, 8), (7, 0), (7, 1), (7, 2), (7, 6), (7, 7), (7, 8), (8, -2), (8, 3), (8, 5), (8, 10), (9, -2), (9, 3), (9, 5), (9, 10), (10, -2), (10, 3), (10, 5), (10, 10), (12, 0), (12, 1), (12, 2), (12, 6), (12, 7), (12, 8)]


#neighbours of the point without itself
#uglier but faster than list comprehension and it's used all the time
def neighbours(point):
    return ((point[0] + 1, point[1] + 1), 
            (point[0], point[1] + 1), 
            (point[0] + 1, point[1]), 
            (point[0] - 1, point[1] + 1), 
            (point[0] + 1, point[1] - 1), 
            (point[0] - 1, point[1] - 1), 
            (point[0], point[1] - 1), 
            (point[0] - 1, point[1]))

#the dialog for changing the REVIVES, SURVIVES properties of the game
class Dialog(simpledialog.Dialog):

    def body(self, master):

        Label(master, text="Revives:").grid(row=0)
        Label(master, text="Survives:").grid(row=1)

        self.e1 = Entry(master)
        self.e2 = Entry(master)

        self.e1.grid(row=0, column=1)
        self.e2.grid(row=1, column=1)
        return self.e1

    def apply(self):
        global REVIVES
        REVIVES = tuple([int(_) for _ in self.e1.get().split(",")])
        global SURVIVES
        SURVIVES = tuple([int(_) for _ in self.e2.get().split(",")])

#class that keeps the points and is a little bit useless                        
class World(object):
    #we keep the living cells in set, because we don't want to get same point twice
    #and we want to get them fast
    def __init__(self, points_alive):
        self.points_alive = set(points_alive)
    
    #checks if there all cells are alive
    def over(self):
        if self.points_alive == set(): return True
        else: return False
    
    #creates the world after one round
    #it's slow ? :(
    def update(self):
        new_points_alive = set()
        for point in self.points_alive:
            num_alive = sum(1 for _ in neighbours(point) if _ in self.points_alive)
            #cells that survive
            if num_alive in SURVIVES: new_points_alive.add(point)
            for p in neighbours(point):
                if not p in self.points_alive:
                    num_alive = sum(1 for _ in neighbours(p) if _ in self.points_alive)
                    #cells that revive:
                    if num_alive in REVIVES: new_points_alive.add(p)
        self.points_alive = new_points_alive

#the main class
class Game(object):
    def __init__(self):
        self.tk = Tk()

        #the default speed of the game
        self.speed = 300
        #the default figure-coloring (we don't color anything if the user doesn't want us to)
        self.check = 0
        self.board = None
        self.init_menu()
        self.welcome = Label(self.tk, padx = 200, pady = 200, text = "Welcome to Matey Neykov's Game of Life")
        self.welcome.pack()

        self.tk.mainloop()
        
    def init_menu(self):
        menu = Menu(self.tk)        
        
        file = Menu(menu)
        file.add_command(label = "About", command = lambda: messagebox.showinfo("About", "GOL!!!"))
        file.add_separator()
        file.add_command(label = "Exit", command = self.tk.quit)
        menu.add_cascade(label = "File", menu = file)
        
        speed = Menu(menu)
        speed.add_command(label = "Slow", command = self.open_speed_game(0))
        speed.add_command(label = "Medium", command = self.open_speed_game(1))
        speed.add_command(label = "Fast", command = self.open_speed_game(2))
        menu.add_cascade(label = "Speed", menu = speed)
        
        game = Menu(menu)
        game.add_command(label = "New", command = self.open_new_game())
        game.add_command(label = "Next", command = self.open_next_generation())
        game.add_command(label = "Start", command = self.open_start_game())
        game.add_command(label = "Stop", command = self.open_stop_game())
        menu.add_cascade(label = "Game", menu = game)

        values = Menu(menu)
        values.add_command(label = "Change", command = self.open_change_life_behaviour())
        menu.add_cascade(label = "Life Behavior", menu = values)

        figures = Menu(menu)
        figures.add_command(label = "Check", command = self.open_check_figures(1))
        figures.add_command(label = "Don't check", command = self.open_check_figures(0))
        menu.add_cascade(label = "Figures", menu = figures)
        
        self.tk.config(menu = menu)
        
    def open_new_game(self):
        return lambda: self.new_game()

    #initializes the board for new game and destroys previous boards if any
    def new_game(self):
        if self.welcome:
            self.welcome.pack_forget()
            self.welcome.destroy()
        if self.board:
            self.board.hide()
        self.board = Board(World([]), self.tk, self.speed, self.check)
        self.board.pack()
    
    def open_next_generation(self):
        return lambda: self.next_generation()

    #this function draws the next generation
    def next_generation(self):
        if self.board == None or self.board.world.over(): return
        self.board.world.update()
        self.board.redraw()
            
    def open_start_game(self):
        return lambda: self.start_game()

    #let the games begin
    def start_game(self):
        if self.board == None: return
        self.tk.after(100, self.board.next_frame)
    
    def open_speed_game(self, speed):
        return lambda: self.speed_game(speed)

    #we have 3 different speeds, and we change them here
    def speed_game(self, speed):
        speeds = [300, 150, 50]
        self.speed = speeds[speed]

    def open_stop_game(self):
        return lambda: self.stop_game()

    #stops the game, it's like new game, but it doesn't destroy the world
    def stop_game(self):
        if self.board == None: return
        tmp = self.board.world
        self.new_game()
        self.board.world = tmp
        self.board.redraw()

    def open_change_life_behaviour(self):
        return lambda: self.change_life_behaviour()

    #here we change the rules of the game
    def change_life_behaviour(self):
        Dialog(self.tk)

    def open_check_figures(self, check):
        return lambda: self.check_figures(check)

    #sets if we want to color the figures or not
    def check_figures(self, check):
        self.check = check

#the board of the game, and the functions coloring the points
class Board(Frame):
    def __init__(self, world, root, speed, check):
        Frame.__init__(self, root)
        self.speed = speed
        self.world = world
        self.canvas = Canvas(root, width = 640, height = 480)
        self.canvas.pack()
        #sets which function to use when redrawing the board; redraw_check colors the figures
        if check: self.redraw = self.redraw_check
        self.rect_dict = {}
        #draws the board and updates the rect_dict
        for i in range(64):
            for j in range(48):
                rect = self.canvas.create_rectangle(i*SIZE, j*SIZE, (i+1)*SIZE, (j+1)*SIZE, fill = "grey", tags = "G")
                #the rect_dict helps, to find the rect on which to use itemconfig
                self.rect_dict.update({(i, j) : rect})
                
        root.bind("<Button-1>", self.handle_button_1)
#I don't know why but the following commented lines doesn't work
#        for direction in (DOWN, UP, RIGHT, LEFT):
#            root.bind("<" + direction + ">", lambda _: self.handle_arrow_keys(direction))   
        root.bind("<Up>", lambda _: self.handle_arrow_keys(UP))
        root.bind("<Left>", lambda _: self.handle_arrow_keys(LEFT))
        root.bind("<Right>", lambda _: self.handle_arrow_keys(RIGHT))
        root.bind("<Down>", lambda _: self.handle_arrow_keys(DOWN))

    #destroys the current board to start a new game
    def hide(self):
        self.canvas.pack_forget()
        self.canvas.destroy()
        self.pack_forget()
        self.destroy()
        
    def handle_button_1(self, event):
        i = event.x // SIZE
        j = event.y // SIZE
        if not (0 <= i <= 63 and 0 <= j <= 47):
            return
        rect = self.rect_dict[(i, j)]
        if (i, j) not in self.world.points_alive:
            new_point = (i, j)
            self.world.points_alive.add(new_point)
            self.canvas.itemconfig(rect, fill = "yellow", tags = ("Y", (i, j)))
        else:
            self.world.points_alive.remove((i, j))
            self.canvas.itemconfig(rect, fill = "grey", tags = ("G", (i, j)))

    def handle_arrow_keys(self, direction):
        points = self.world.points_alive
        new_points = set()
        for point in points:
            new_points.add((PLACEMENT[direction][0]*10 + point[0], PLACEMENT[direction][1]*10 + point[1]))
        self.world.points_alive = new_points
        self.redraw()

    #searches and colors a certain figure
    def check(self, figure, not_figure = [], color = "blue"):
        for point in self.world.points_alive:
            #this checks the points not far away from the displayed part of the board
            if not (-150 <= point[0]*SIZE <= 780 and -150 <= point[1]*SIZE <= 620):
                continue
            is_figure = 1
            is_not_figure = 1
            #checks if there is a match with the figure
            for _ in figure:
                if (_[0] + point[0], _[1] + point[1]) not in self.world.points_alive:
                    is_figure = 0
                    break
            #optimizes a bit, because we don't have many figures
            if not is_figure:
                continue
            #checks the other conditions if we have a match from above
            for _ in not_figure:
                if (_[0] + point[0], _[1] + point[1]) in self.world.points_alive:
                    is_not_figure = 0
                    break
            if is_figure and is_not_figure:
                #we have a certain figure here so we color the visible part of it in the given color
                for _ in figure:
                    if 0 <= (point[0] + _[0])*SIZE <= 630 and 0 <= (point[1] + _[1])*SIZE <= 470:
                        rect = self.rect_dict[(point[0] + _[0], point[1] + _[1])]
                        self.canvas.itemconfig(rect, fill = color)

    #redraws the board, but it doesn't check for figures
    def redraw(self):
        for rect in self.canvas.find_withtag("Y"):
            self.canvas.itemconfig(rect, fill = "grey", tags = "G")
        for point in self.world.points_alive:
            if 0 <= point[0]*SIZE <= 630 and 0 <= point[1]*SIZE <= 470:
                rect = self.rect_dict[(point[0], point[1])]
                self.canvas.itemconfig(rect, fill = "yellow", tags = "Y")

    #it's the same as redraw, but it checks and colors the figures
    def redraw_check(self):
        for rect in self.canvas.find_withtag("Y"):
            self.canvas.itemconfig(rect, fill = "grey", tags = "G")
        for point in self.world.points_alive:
            if 0 <= point[0]*SIZE <= 630 and 0 <= point[1]*SIZE <= 470:
                rect = self.rect_dict[(point[0], point[1])]
                self.canvas.itemconfig(rect, fill = "yellow", tags = "Y")
        #it's ugly but i think it's faster
        self.check(TOAD1, NOT_TOAD1, "brown")
        self.check(TOAD2, NOT_TOAD2, "brown")
        self.check(TOAD3, NOT_TOAD3, "brown")
        self.check(TOAD4, NOT_TOAD4, "brown")
        self.check(TOAD5, NOT_TOAD5, "brown")
        self.check(TOAD6, NOT_TOAD6, "brown")
        self.check(TOAD7, NOT_TOAD7, "brown")
        self.check(TOAD8, NOT_TOAD8, "brown")
        self.check(GLIDERDL1, NOT_GLIDERDL1)
        self.check(GLIDERDL2, NOT_GLIDERDL2)
        self.check(GLIDERDL3, NOT_GLIDERDL3)
        self.check(GLIDERDL4, NOT_GLIDERDL4)
        self.check(GLIDERDR1, NOT_GLIDERDR1)
        self.check(GLIDERDR2, NOT_GLIDERDR2)
        self.check(GLIDERDR3, NOT_GLIDERDR3)
        self.check(GLIDERDR4, NOT_GLIDERDR4)
        self.check(GLIDERUL1, NOT_GLIDERUL1)
        self.check(GLIDERUL2, NOT_GLIDERUL2)
        self.check(GLIDERUL3, NOT_GLIDERUL3)
        self.check(GLIDERUL4, NOT_GLIDERUL4)
        self.check(GLIDERUR1, NOT_GLIDERUR1)
        self.check(GLIDERUR2, NOT_GLIDERUR2)
        self.check(GLIDERUR3, NOT_GLIDERUR3)
        self.check(GLIDERUR4, NOT_GLIDERUR4)
        self.check(LWSSL1, NOT_LWSSL1, "red")
        self.check(LWSSL2, NOT_LWSSL2, "red")
        self.check(LWSSL3, NOT_LWSSL3, "red")
        self.check(LWSSL4, NOT_LWSSL4, "red")
        self.check(LWSSR1, NOT_LWSSR1, "red")
        self.check(LWSSR2, NOT_LWSSR2, "red")
        self.check(LWSSR3, NOT_LWSSR3, "red")
        self.check(LWSSR4, NOT_LWSSR4, "red")
        self.check(BLOCK, NOT_BLOCK, "orange")
        self.check(BLINKER1, NOT_BLINKER1, "green")
        self.check(BLINKER2, NOT_BLINKER2, "green")
        self.check(BOAT1, NOT_BOAT1, "pink")
        self.check(BOAT2, NOT_BOAT2, "pink")
        self.check(BOAT3, NOT_BOAT3, "pink")
        self.check(BOAT4, NOT_BOAT4, "pink")
        self.check(DAEMONS1, color = "purple")
        self.check(DAEMONS2, color = "purple")
        self.check(DAEMONS3, color = "purple")

    def next_frame(self):
        self.world.update()
        self.redraw()
        self.after(self.speed, self.next_frame)
            
if __name__ == "__main__":
    Game()