from random import randint
from tkinter import *

def rules(ev = None):
    Regles = Tk()
    Regles.title('Règles du jeu')
    Regles.iconbitmap("icone.ico")
    ReglesCanvas = Canvas(Regles , width = 500 , height = '200' , bg = 'white')
    ReglesCanvas.pack()
    ReglesCanvas.create_text(5,5,anchor='nw',text="Le jeu de la vie n'est pas réellement un jeu.\nIl s'agit plutôt d'un automate.\nOn place un certain nombre de cases sur une grille. Chacune représente une cellule.\nÀ chaque étape,\n-- si une cellule est morte (non colorée) et possède exactement trois voisines vivantes,\nalors elle devient vivante (elle naît) : on la colore.\n-- si une cellule vivante (colorée) posséde deux ou trois voisines vivantes, alors elle reste\nvivante; sinon elle meurt.")

def pointeur(event):
    message.set('')
    j = event.x // dim_px
    i = event.y // dim_px
    if grille[i][j] == 1:
        grille[i][j] = 0
        GrilleCanvas.create_rectangle(5 + dim_px * j, 5 + dim_px * i, 3 + dim_px * (j + 1), 3 + dim_px * (i + 1),
                                      fill='white')
    else:
        grille[i][j] = 1
        GrilleCanvas.create_rectangle(5 + dim_px * j, 5 + dim_px * i, 3 + dim_px * (j + 1), 3 + dim_px * (i + 1),
                                      fill='red')

def init_grille(config=None):
    global grille, changed_cells
    grille = [[0 for i in range(nb_colonnes)] for j in range(nb_lignes)]
    changed_cells = set()  # Réinitialiser les cellules modifiées
    if not config:  # aléatoire
        i = nb_lignes // 2
        j = nb_colonnes // 2
        grille[i][j] = 1
        changed_cells.add((i, j))  # Ajouter la cellule initiale à l'ensemble des cellules modifiées
        for k in range(randint(4, 10)):
            x, y = randint(0, 1), randint(0, 1)
            if x == 0:
                i += 1
            else:
                i -= 1
            if y == 0:
                j += 1
            else:
                j -= 1
            if i < 0:
                i = 0
            if i == nb_lignes:
                i = nb_lignes - 1
            if j < 0:
                j = 0
            if j == nb_colonnes:
                j = nb_colonnes - 1

            grille[i][j] = 1
            changed_cells.add((i, j))  # Ajouter la cellule modifiée à l'ensemble
    elif config == 'clignotant':
        grille[nb_lignes // 2][nb_colonnes // 2] = 1
        grille[nb_lignes // 2][nb_colonnes // 2 - 1] = 1
        grille[nb_lignes // 2][nb_colonnes // 2 + 1] = 1
        for i in range(nb_lignes):
            for j in range(nb_colonnes):
                if grille[i][j] == 1:
                    changed_cells.add((i, j))  # Ajouter la cellule initiale à l'ensemble des cellules modifiées


def init_grille_voisins():
    global grille_voisins
    grille_voisins = [ [ 0 for i in range( nb_colonnes )] for j in range( nb_lignes ) ]
    for i in range(nb_lignes):
        for j in range(nb_colonnes):
            nombre_voisins(i, j)

def is_grid_full():
    for i in range( nb_lignes ):
        for j in range( nb_colonnes ):
            if grille[i][j] != 0:
                return True
    return False

def step(ev = None):
    init_grille_voisins()
    etape_suivante()
    draw_grille_vierge()
    draw_cases()

def etape_suivante():
    global changement, changed_cells
    changement = False
    changed_cells = set()  # Initialiser l'ensemble des cellules modifiées
    for i in range(nb_lignes):
        for j in range(nb_colonnes):
            if grille_voisins[i][j] == 3 and grille[i][j] == 0:
                grille[i][j] = 1
                changement = True
                changed_cells.add((i, j))  # Ajouter la cellule modifiée à l'ensemble
            if grille[i][j] == 1 and grille_voisins[i][j] != 2 and grille_voisins[i][j] != 3:
                grille[i][j] = 0
                changement = True
                changed_cells.add((i, j))  # Ajouter la cellule modifiée à l'ensemble

def nombre_voisins(i , j):
    neighbours = 0
    if i == 0:
        if j == 0:
            if grille[0][1] == 1:
                neighbours += 1
            if grille[1][0] == 1:
                neighbours += 1
            if grille[1][1] == 1:
                neighbours += 1
        elif j == nb_colonnes - 1:
            if grille[0][j-1] == 1:
                neighbours += 1
            if grille[1][j] == 1:
                neighbours += 1
            if grille[1][j-1] == 1:
                neighbours += 1
        else:
            if grille[0][j-1] == 1:
                neighbours += 1
            if grille[0][j+1] == 1:
                neighbours += 1
            if grille[1][j-1] == 1:
                neighbours += 1
            if grille[1][j] == 1:
                neighbours += 1
            if grille[1][j+1] == 1:
                neighbours += 1
    elif i == nb_lignes - 1:
        if j == 0:
            if grille[i][1] == 1:
                neighbours += 1
            if grille[i-1][0] == 1:
                neighbours += 1
            if grille[i-1][1] == 1:
                neighbours += 1
        elif j == nb_colonnes - 1:
            if grille[i][j-1] == 1:
                neighbours += 1
            if grille[i-1][j] == 1:
                neighbours += 1
            if grille[i-1][j-1] == 1:
                neighbours += 1
        else:
            if grille[i][j+1] == 1:
                neighbours += 1
            if grille[i-1][j] == 1:
                neighbours += 1
            if grille[i][j-1] == 1:
                neighbours += 1
            if grille[i-1][j-1] == 1:
                neighbours += 1
            if grille[i-1][j+1] == 1:
                neighbours += 1
    else:
        if j == 0:
            if grille[i-1][0] == 1:
                neighbours += 1
            if grille[i+1][0] == 1:
                neighbours += 1
            if grille[i][1] == 1:
                neighbours += 1
            if grille[i-1][1] == 1:
                neighbours += 1
            if grille[i+1][1] == 1:
                neighbours += 1
        elif j == nb_colonnes - 1:
            if grille[i-1][j] == 1:
                neighbours += 1
            if grille[i+1][j] == 1:
                neighbours += 1
            if grille[i][j-1] == 1:
                neighbours += 1
            if grille[i-1][j-1] == 1:
                neighbours += 1
            if grille[i+1][j-1] == 1:
                neighbours += 1
        else:
            if grille[i-1][j] == 1:
                neighbours += 1
            if grille[i+1][j] == 1:
                neighbours += 1
            if grille[i][j+1] == 1:
                neighbours += 1
            if grille[i][j-1] == 1:
                neighbours += 1
            if grille[i-1][j-1] == 1:
                neighbours += 1
            if grille[i-1][j+1] == 1:
                neighbours += 1
            if grille[i+1][j-1] == 1:
                neighbours += 1
            if grille[i+1][j+1] == 1:
                neighbours += 1

    grille_voisins[i][j] = neighbours

def makeentry(parent, caption, width = None, **options):
    Label(parent, text=caption).pack()
    entry = Entry(parent, **options)
    if width:
        entry.config(width=width)
    entry.pack()
    return entry

def draw_cases():
    global changed_cells
    for cell in changed_cells:
        i, j = cell
        if grille[i][j] == 1:
            GrilleCanvas.create_rectangle(5 + dim_px * j, 5 + dim_px * i, 3 + dim_px * (j + 1), 3 + dim_px * (i + 1), fill='red')
        else:
            GrilleCanvas.create_rectangle(5 + dim_px * j, 5 + dim_px * i, 3 + dim_px * (j + 1), 3 + dim_px * (i + 1), fill='white')
    changed_cells = set()  # Réinitialiser les cellules modifiées après les avoir dessinées

def draw_grille_vierge():
    GrilleCanvas.create_rectangle(3, 3, dim_px * (nb_colonnes + 1) + 3, dim_px * (nb_lignes + 1) + 3, width = 2 , fill = 'white')
    for x in range(nb_colonnes + 1):
        GrilleCanvas.create_line(x * dim_px + 3, 3, x * dim_px + 3, dim_px * (nb_lignes + 1) + 3, width = 2)
    for y in range(nb_lignes + 1):
        GrilleCanvas.create_line(3, y * dim_px + 3, dim_px * (nb_colonnes + 1) + 3, y * dim_px + 3, width = 2)
    GrilleCanvas.create_rectangle(3, 3, dim_px * (nb_colonnes + 1) + 3, dim_px * (nb_lignes + 1) + 3, width = 2)

def grille_aleatoire(ev = None):
    message.set('')
    init_grille()
    draw_grille_vierge()
    draw_cases()

def grille_clignotant(ev = None):
    message.set('')
    init_grille(config = 'clignotant')
    draw_grille_vierge()
    draw_cases()

def validate_dim(ev = None):
    global nb_lignes
    global nb_colonnes
    global delai

    nb_lignes , nb_colonnes, delai = int(lignes.get()) , int(colonnes.get()) , int(delay.get())
    menu.destroy()
    init_grille(config = 'vierge')
    new_window()

def new_window():
    global GrilleCanvas
    global GrilleWindow
    global dim_px
    global message

    """ On affiche la grille """

    dim_px = 20

    GrilleWindow = Tk()
    GrilleWindow.iconbitmap("icone.ico")
    GrilleWindow.title("Le jeu de la vie")

    GrilleCanvas = Canvas(GrilleWindow, width = dim_px * (nb_colonnes + 1) + 3, height = dim_px * (nb_lignes + 1) + 3, bg = 'white')
    GrilleCanvas.grid(padx = 5 , pady = 5)
    GrilleCanvas.bind('<Button-1>',pointeur)

    draw_grille_vierge()

    """ On affiche les boutons """

    # Frame principale
    FrameRight = Frame(GrilleWindow)
    FrameRight.grid(column = 1 , row = 0)

    # Frame boutons
    Boutons = Frame(FrameRight)
    Boutons.grid(column = 0, row = 0)

    Button(Boutons , text='Grille aléatoire' , padx = 10 , command = grille_aleatoire , underline = 7).grid(column = 0 , row = 0)
    Button(Boutons, text='Clignotant', padx = 10, command = grille_clignotant , underline = 0).grid(column = 0, row = 1)
    Button(Boutons, text='Etape Suivante', padx = 10, command = step , underline = 6).grid(column = 0, row = 3)
    Button(Boutons, text='Jouer', padx = 10, command = play , underline = 0).grid(column = 0, row = 4)
    Button(Boutons, text='Stop [ESC]', padx = 10, command = stop, underline = 0).grid(column=0, row = 5)

    # Message éventuel
    message = StringVar()
    Label(FrameRight , text = '').grid(row = 1 , column = 0)
    Label(FrameRight, textvariable = message , fg = 'red' , font = ('Helvetica',12)).grid(row = 2, column = 0)

    # Raccourcis clavier
    GrilleWindow.bind('<a>' , grille_aleatoire)
    GrilleWindow.bind('<A>', grille_aleatoire)
    GrilleWindow.bind('<c>', grille_clignotant)
    GrilleWindow.bind('<C>', grille_clignotant)
    GrilleWindow.bind('<s>', step)
    GrilleWindow.bind('<S>', step)
    GrilleWindow.bind('<j>', play)
    GrilleWindow.bind('<J>', play)
    GrilleWindow.bind('<Escape>a', play)

    GrilleWindow.mainloop()

def stop(ev = None):
    GrilleWindow.after_cancel(boucle)

def play(ev = None):
    global boucle
    step()

    if changement:
        boucle = GrilleWindow.after(delai , play)
    else:
        if is_grid_full():
            message.set('Etat stationnaire')
        else:
            message.set('Destruction totale')

""" Début du programme """

if __name__ == '__main__':
    menu = Tk()
    menu.title('Le jeu de la vie - Stéphane Pasquet - mathweb.fr')
    menu.iconbitmap("icone.ico")

    menuCanvas = Canvas(menu, width=350, height=50)
    menuCanvas.pack()

    numLigne = IntVar()
    numLigne.set(20)
    numCol = IntVar()
    numCol.set(30)
    numDelai = IntVar()
    numDelai.set(100)

    Label(menu, text="Le jeu de la vie", font=("Helvetica", 20)).pack()

    lignes = makeentry(menu, "Nombre de lignes :", 2, textvariable = numLigne)
    colonnes = makeentry(menu, "\nNombre de colonnes :", 2, textvariable = numCol)
    delay = makeentry(menu, "\nDélai pour les animations (en ms) :", 3, textvariable = numDelai)

    Label(menu, text='\n').pack()
    Button(text='Valider', command = validate_dim , underline = 0).pack()
    menu.bind('<v>' , validate_dim)
    menu.bind('<V>' , validate_dim)
    Label(menu, text='\n').pack()
    Button(text='Règles du jeu', command = rules , underline = 0).pack()
    menu.bind('<r>' , rules)
    menu.bind('<R>' , rules)
    Label(menu, text='\n').pack()

    menu.mainloop()