# ==============================================================================
"""SLIDE : a color-based slide puzzle game"""
# ==============================================================================
__author__  = "Christophe Schlick modified by Philippe Blasi"
__version__ = "0.0" # skeleton version
__date__    = "2022-11-12"
# ==============================================================================
from ezTK import *
from random import shuffle, randrange as rr
# ------------------------------------------------------------------------------
def main():
  """create the main window and pack the widgets"""
  global win
  win = Win(title='SLIDE', op=5, click=on_click) # create main window
  colors = ('#FFF','#F00','#0F0','#07F','#FF0') # colors for board cells
  # ----------------------------------------------------------------------------
  # TODO (use 1 Frame with 2 Buttons, 1 Frame with 25 Bricks)
  # ----------------------------------------------------------------------------
  # Frame pour les deux boutons
  fr = Frame(win,op=0)
  Button(fr,text='SHUFFLE',command=on_shuffle)
  Button(fr,text='RESET',command=on_reset)
  # ----------------------------------------------------------------------------
  # Frame pour les 25 briques avec un fold de 5. On l'apelle win.board
  board = Frame(win,op=0,fold=5)

  # la remplir de 25 Bricks
  for i in range(25):
    Brick(board, height= 64, width= 64, bg=colors, border=1)
  win.board = board # sauvegarde de la frame board

  # ----------------------------------------------------------------------------
  # créer ici win.grid, tableau de 5 lignes et 5 colonnes
  win.grid = [[0 for col in range(5)] for row in range(5)]
  # ----------------------------------------------------------------------------
  on_shuffle(); win.loop() # set random configuration and start event loop
# ------------------------------------------------------------------------------
def on_shuffle() -> None:
  """callback function for the "SHUFFLE" button"""
  # TODO (use 'win.cells' a 1D list of integers to store initial cell states)
  # TODO (create random board configuration by shuffling the previous list)
  win.cells = [0] + [1,2,3,4] * 6
  shuffle(win.cells)
  on_reset()
# ------------------------------------------------------------------------------
def on_reset() -> None:
  """callback function for the "RESET" button"""
  # TODO (reset board configuration to its initial state)
  # TODO (use 'win.grid' a 2D list of integers to store current board config)
  for n in range(25):
    row, col = n//5, n%5
    win.grid[row][col] = win.cells[n]
  show()
# ------------------------------------------------------------------------------
def on_click(widget:object, code:str, mods:str) -> None:
  """callback function for all keyboard events"""
  if widget.master != win.board: return # wrong click (= not on a board cell)
  row, col = widget.index; move(row, col) # apply move on selected cell
# ------------------------------------------------------------------------------
def move(row:int, col:int) -> None:
  """move cell located at (row,col) to its neighboring empty cell (if any)"""
  # TODO (look at the 4 neighboring cells to check if one is the empty cell)
  # TODO (then move the selected cell to the location of the empty cell)
  # on fait le travail sur win.grid, qui sera ensuite affiché par la fonction show

  if row-1 >= 0 and win.grid[row-1][col] == 0:
    # échanger les deux valeurs
    win.grid[row][col], win.grid[row-1][col] = win.grid[row-1][col], win.grid[row][col]
  elif row+1 < 5 and win.grid[row+1][col] == 0:
    # échanger les deux valeurs
    win.grid[row][col], win.grid[row+1][col] = win.grid[row+1][col], win.grid[row][col]
  # même chose pour row+1, col-1 et col+1  
  
  if check(): win.grid = []; victory() # start animation if victory
  else: show() # show new board configuration after cell displacement
# ------------------------------------------------------------------------------
def show() -> None:
  """show current game board by setting state defined for each grid cell"""
  for n in range(25): # loop over grid cells and change state of board cells
    row, col = n//5, n%5; win.board[row][col].state = win.grid[row][col]
# ------------------------------------------------------------------------------
def check() -> bool:
  """check victory by counting number of peer cells with same state"""
  for loop in range(25): # loop over all board cells
    if loop == 12: continue # skip central cell (should be empty, anyway)
    row, col = loop//5, loop%5 # get cell coords by Euclidian division
    peers = [win.grid[row+r][col+c] for r,c in ((1,0),(-1,0),(0,1),(0,-1))
      if 0 <= row+r <= 4 and 0 <= col+c <= 4] # get states for all peer cells
    if peers.count(win.grid[row][col]) not in (2,3): return False # no victory
  return True # all cells have 2 or 3 peers with same state ==> victory
# ------------------------------------------------------------------------------
def victory() -> None:
  """play victory animation"""
  win.board[rr(5)][rr(5)].state = rr(1,5) # change color of random grid cell
  if not win.grid: win.after(10, victory) # launch 'victory' every 10 ms
# ==============================================================================
if __name__ == '__main__':
  main()
# ==============================================================================
