# 1.00 - Initial version
# 1.01 - Fixed maze start cell
# 1.02 - Added 'dust' to show difference between visited and unvisited trails.
# 1.03 - Added settings support
# 1.04 - Added animation
# 1.05 - Small fix to active cell selection
#
# Description:
#   Find your way to the exit of the maze.
#   Longer lingering lowers your score, but eating fruits on your path
#   adds to score. Wander carefully. Success!
#
# Usage:
#   Use buttons at the side of to move from cell to cell until you find
#   the exit. Orange cells are fruits. Red cell your starting point and
#   a green cell the exit.
#
# Button:
#   Mid top buttons    - move north
#   Mid left buttons   - move west
#   Mid right buttons  - move east
#   Mid bottom buttons - move south
#
# Touch pad:
#   Left / Right - select maze
#
import display, keypad, touchpads, appconfig, virtualtimers

KEY_MOVE_NORTH_0 =  1
KEY_MOVE_NORTH_1 =  2
KEY_MOVE_SOUTH_0 = 13
KEY_MOVE_SOUTH_1 = 14
KEY_MOVE_EAST_0  =  7
KEY_MOVE_EAST_1  = 11
KEY_MOVE_WEST_0  =  4
KEY_MOVE_WEST_1  =  8

DEF_MAZE = [[0x4A, 0x4C, 0x1C, 0x4C, 0x49,
             0x86, 0x4C, 0x48, 0x49, 0x47,
             0x4A, 0x49, 0x43, 0x46, 0x89,
             0x43, 0x46, 0x44, 0x4D, 0x43,
             0x46, 0x4D, 0x2E, 0x4C, 0x45],
            [0x4E, 0x4C, 0x48, 0x49, 0x4B, 0x4A, 0x48, 0xCC, 0x4C, 0x4C, 0x49, 0x4A, 0x4C, 0x49, 0x4A, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4D, 0xCA, 0xCC, 0x48, 0x4C, 0x4C, 0x49, 0x4E, 0x40, 0x69,
             0x4A, 0x4C, 0x45, 0x43, 0x46, 0x45, 0x46, 0x4C, 0x4C, 0x49, 0x43, 0x46, 0x49, 0x43, 0x43, 0x4A, 0x4C, 0x4C, 0xCC, 0xCC, 0xCC, 0x45, 0x4B, 0x43, 0x4C, 0x4C, 0x44, 0x49, 0x47, 0x43,
             0x43, 0xCB, 0xCA, 0x45, 0x4A, 0x4C, 0x4C, 0x4C, 0x49, 0x43, 0x46, 0x4C, 0x45, 0x43, 0x43, 0x46, 0x4C, 0x48, 0x4C, 0x4C, 0x4C, 0x49, 0x43, 0x46, 0x4C, 0x49, 0x4B, 0x46, 0xCC, 0x41,
             0x43, 0x46, 0x41, 0x4A, 0x45, 0x4A, 0x4C, 0x49, 0x46, 0x45, 0x4A, 0x49, 0x4A, 0x45, 0x46, 0x4C, 0x4C, 0x45, 0x4A, 0x49, 0x4A, 0x45, 0x42, 0x49, 0x4A, 0x45, 0x42, 0x4C, 0xC9, 0x43,
             0x42, 0x49, 0x43, 0x43, 0x4E, 0x41, 0x4A, 0x44, 0x4C, 0x49, 0x43, 0x46, 0x45, 0xCE, 0x48, 0x4C, 0x4C, 0x4C, 0x45, 0x43, 0x46, 0x48, 0x45, 0x47, 0x46, 0x49, 0x43, 0x4E, 0x41, 0x43,
             0x47, 0x43, 0x43, 0x46, 0x49, 0x43, 0x46, 0x4C, 0x49, 0x47, 0xC3, 0x4E, 0x48, 0x4C, 0x45, 0x4B, 0x4A, 0x49, 0x4A, 0x45, 0x4A, 0x45, 0x4A, 0x4C, 0x49, 0x43, 0x46, 0x49, 0x43, 0x43,
             0x4A, 0x45, 0x46, 0x4C, 0x45, 0x43, 0x4E, 0x49, 0x46, 0x4C, 0x44, 0x4C, 0x45, 0x4A, 0x49, 0x42, 0x45, 0x46, 0x45, 0x4A, 0x45, 0x4A, 0x45, 0x4E, 0x44, 0x45, 0x4A, 0x45, 0x47, 0x43,
             0x43, 0x4A, 0x4C, 0x4C, 0x49, 0x46, 0x4C, 0x41, 0x4E, 0x49, 0x4A, 0x4C, 0x49, 0x47, 0x42, 0x45, 0x4A, 0x4C, 0x49, 0x43, 0x4B, 0x46, 0x4C, 0xCC, 0xC8, 0x49, 0xC6, 0x48, 0x49, 0x43,
             0x42, 0x45, 0x4A, 0x49, 0x46, 0x4E, 0x49, 0x42, 0x4C, 0x45, 0x46, 0x49, 0x46, 0x49, 0x43, 0x4E, 0x44, 0x49, 0x47, 0x43, 0x42, 0x49, 0x4A, 0x49, 0x47, 0x46, 0x49, 0x43, 0x43, 0x43,
             0x46, 0x49, 0x47, 0x43, 0x46, 0x49, 0x46, 0x45, 0x4A, 0x4C, 0x4C, 0x44, 0x4D, 0x43, 0x46, 0x49, 0x4A, 0x45, 0x4A, 0x44, 0x45, 0x47, 0x43, 0x46, 0x4C, 0x4C, 0x45, 0x47, 0x42, 0x45,
             0x4B, 0x46, 0x49, 0x42, 0xC6, 0x46, 0x4C, 0xCC, 0x45, 0x4B, 0xCA, 0x4C, 0x4C, 0x45, 0x4E, 0x41, 0x46, 0x4C, 0x44, 0x49, 0x4A, 0x4C, 0x41, 0x4A, 0x4C, 0x49, 0x4A, 0x49, 0x46, 0x4D,
             0x46, 0x48, 0x45, 0x47, 0x4C, 0x4C, 0x4C, 0x4C, 0x48, 0x41, 0x43, 0x4A, 0x4C, 0x4C, 0x49, 0x43, 0x4A, 0x4C, 0x49, 0x43, 0x43, 0x4E, 0x45, 0x43, 0xCB, 0x46, 0x45, 0x43, 0x4A, 0x49,
             0x4B, 0x43, 0x4A, 0x4C, 0x4C, 0x4C, 0x49, 0x4A, 0x41, 0x47, 0x46, 0x45, 0x4A, 0x4C, 0x45, 0x43, 0x46, 0x49, 0x43, 0x47, 0x46, 0x4C, 0x49, 0x46, 0x40, 0x4C, 0xCD, 0x46, 0xC5, 0x43,
             0x43, 0x46, 0x45, 0x4A, 0x49, 0x49, 0x46, 0x45, 0x43, 0x4A, 0x4C, 0x4C, 0x45, 0x4B, 0x4A, 0x45, 0x4A, 0x45, 0x46, 0x49, 0x4A, 0x49, 0x42, 0x4D, 0x43, 0x4A, 0x49, 0x4A, 0x49, 0x43,
             0x43, 0x4B, 0x4A, 0x44, 0x46, 0x47, 0x4A, 0x4C, 0x45, 0x46, 0x4C, 0x4C, 0x49, 0x46, 0x44, 0xCC, 0x45, 0x4A, 0x49, 0x43, 0x43, 0x46, 0x45, 0x4A, 0x45, 0x43, 0x46, 0x45, 0x46, 0x41,
             0x43, 0x43, 0x42, 0x4D, 0x46, 0x48, 0x45, 0x4E, 0x49, 0x4A, 0x48, 0x4D, 0x46, 0x4C, 0x49, 0xCB, 0x4A, 0x45, 0x43, 0x43, 0x42, 0x4C, 0x49, 0x46, 0xC9, 0x43, 0x4B, 0x4A, 0x4C, 0x45,
             0x43, 0x43, 0x43, 0x4A, 0x49, 0x46, 0x4C, 0x49, 0x46, 0x45, 0x46, 0x48, 0x4C, 0x49, 0x43, 0x46, 0x41, 0x4B, 0x43, 0x43, 0x42, 0x4D, 0x46, 0x4C, 0x41, 0x43, 0x43, 0x46, 0x49, 0x4B,
             0x43, 0x46, 0x41, 0x43, 0x46, 0x4C, 0x49, 0x43, 0x4A, 0x4C, 0x49, 0x43, 0x4E, 0x45, 0x42, 0x49, 0x46, 0x45, 0x42, 0x45, 0x43, 0x4A, 0x4C, 0x49, 0x47, 0x43, 0x42, 0x49, 0x43, 0x43,
             0x46, 0x49, 0x47, 0x46, 0x49, 0x4B, 0x43, 0x46, 0x45, 0x4E, 0x44, 0x45, 0x4A, 0x49, 0x47, 0x46, 0x4C, 0x49, 0x43, 0x4E, 0x45, 0x43, 0x4B, 0x46, 0x49, 0x43, 0x43, 0x47, 0x43, 0x43,
             0x5E, 0x44, 0x4C, 0x4C, 0xC5, 0x46, 0x44, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x45, 0x46, 0x4C, 0x4C, 0x4C, 0x45, 0x46, 0x4C, 0x4C, 0x45, 0x46, 0x4C, 0x44, 0x45, 0x46, 0x4C, 0x44, 0x45],
            [0x4A, 0x4C, 0x1C, 0x4C, 0x49,
             0x86, 0x4C, 0x48, 0x49, 0x47,
             0x4A, 0x49, 0x43, 0x46, 0x89,
             0x43, 0x46, 0x44, 0x4D, 0x43,
             0x46, 0x4D, 0x2E, 0x4C, 0x45]]

DEF_MAX_X = [ 5,30, 5]
DEF_MAX_Y = [ 5,20, 5]
DEF_INI_X = [ 2, 0, 2]
DEF_INI_Y = [ 0,19, 0]

settings = appconfig.get('maze', {'colours': {'empty' : '000000', 'wall' : '0000FF', 'entry' : 'FF0000', 'exit' : '00FF00', 'dust' : '138535', 'fruit' : 'FFC000'}, 'animationrate': 500})

g_colours = {}
g_animation_even = False
g_animation_rate = settings['animationrate']

g_maze = []
g_maze_cell  = 0
g_maze_index = 0
g_maze_max_x = 5
g_maze_max_y = 5

g_player_pos_x = 2
g_player_pos_y = 0
g_player_score = 0
g_player_steps = 0

def init_app():
  global g_colours
  print('Initializing App Maze')
  for key, colour in settings['colours'].items():
    g_colours[key] = int(colour, 16)

def init_maze():
  global g_maze, g_maze_cell
  global g_maze_max_x, g_maze_max_y
  global g_player_pos_x, g_player_pos_y
  global g_player_score, g_player_steps
  
  print('Initializing Maze %s' % (g_maze_index + 1))
  
  g_maze_max_x = DEF_MAX_X[g_maze_index]
  g_maze_max_y = DEF_MAX_Y[g_maze_index]
  g_player_pos_x = DEF_INI_X[g_maze_index]
  g_player_pos_y = DEF_INI_Y[g_maze_index]
  g_player_score = 0
  g_player_steps = 0
  
  g_maze = []  
  for idx in range(0,len(DEF_MAZE[g_maze_index])):
    g_maze.append(DEF_MAZE[g_maze_index][idx])

  g_maze_cell = g_maze[g_maze_max_x * g_player_pos_y + g_player_pos_x]
  
def update_maze(x, y):
  global g_maze, g_maze_cell, g_player_steps, g_player_score
  
  g_player_steps = g_player_steps + 1
  
  g_maze_cell = g_maze[g_maze_max_x * y + x]
  
  if g_maze_cell & 0x40: #Dust
    g_maze[g_maze_max_x * y + x] = g_maze_cell & ~0x40
  
  if g_maze_cell & 0x80: #Fruit! :P
    g_maze[g_maze_max_x * y + x] = g_maze_cell & ~0x80
    g_player_score = g_player_score + 10
    
  if g_maze_cell & 0x20: #Exit
    g_player_score = g_player_score + (100 - g_player_steps)
    
  print("(%s,%s) step: %s  score: %s" % (g_player_pos_x, g_player_pos_y, g_player_steps, g_player_score))

def refresh_display(x, y):
  global g_maze_cell
  g_maze_cell = g_maze[g_maze_max_x * y + x]
  
  for idx in range(0,16):
    display.drawPixel(idx %  4, idx // 4, g_colours['empty'])
	
  if g_maze_cell & 0x40: #Dust
    for idx in [5,6,9,10]:
      display.drawPixel(idx %  4, idx // 4, g_colours['dust'])

  if g_maze_cell & 0x08: #North
    display.drawPixel(0, 0, g_colours['wall'])
    display.drawPixel(1, 0, g_colours['wall'])
    display.drawPixel(2, 0, g_colours['wall'])
    display.drawPixel(3, 0, g_colours['wall'])

  if g_maze_cell & 0x04: #South
    display.drawPixel(0, 3, g_colours['wall'])
    display.drawPixel(1, 3, g_colours['wall'])
    display.drawPixel(2, 3, g_colours['wall'])
    display.drawPixel(3, 3, g_colours['wall'])
	
  if g_maze_cell & 0x02: #West
    display.drawPixel(0, 0, g_colours['wall'])
    display.drawPixel(0, 1, g_colours['wall'])
    display.drawPixel(0, 2, g_colours['wall'])
    display.drawPixel(0, 3, g_colours['wall'])

  if g_maze_cell & 0x01: #East
    display.drawPixel(3, 0, g_colours['wall'])
    display.drawPixel(3, 1, g_colours['wall'])
    display.drawPixel(3, 2, g_colours['wall'])
    display.drawPixel(3, 3, g_colours['wall'])

  display.flush()
  
def animate():
  global g_animation_even
  
  g_animation_even = not g_animation_even

  animation = False
  colour = 0x000000
  if   g_maze_cell & 0x80: #Fruit!
    animation = True
    colour = g_colours['fruit']
  elif g_maze_cell & 0x20: #Exit
    animation = True
    colour = g_colours['exit']
  elif g_maze_cell & 0x10: #Entry
    animation = True
    colour = g_colours['entry']
    
  if animation:
    if g_animation_even:
      for idx in [5,10]:
        display.drawPixel(idx %  4, idx // 4, colour)
      for idx in [6,9]:
        display.drawPixel(idx %  4, idx // 4, g_colours['empty'])
    else:
      for idx in [5,10]:
        display.drawPixel(idx %  4, idx // 4, g_colours['empty'])
      for idx in [6,9]:
        display.drawPixel(idx %  4, idx // 4, colour)
    display.flush()
      
  return g_animation_rate
	
def on_key(index, pressed):
  global g_player_pos_x, g_player_pos_y
  
  if pressed:
    if   (index == KEY_MOVE_NORTH_0 or index == KEY_MOVE_NORTH_1) and not (g_maze_cell & 0x08):
      if g_player_pos_y > 0:
        g_player_pos_y = g_player_pos_y - 1
    elif (index == KEY_MOVE_SOUTH_0 or index == KEY_MOVE_SOUTH_1) and not (g_maze_cell & 0x04):
      if g_player_pos_y < g_maze_max_y:
        g_player_pos_y = g_player_pos_y + 1
    elif (index == KEY_MOVE_WEST_0 or index == KEY_MOVE_WEST_1) and not (g_maze_cell & 0x02):
      if g_player_pos_x > 0:
        g_player_pos_x = g_player_pos_x - 1
    elif (index == KEY_MOVE_EAST_0 or index == KEY_MOVE_EAST_1) and not (g_maze_cell & 0x01):
      if g_player_pos_x < g_maze_max_x:
        g_player_pos_x = g_player_pos_x + 1
  
    refresh_display(g_player_pos_x, g_player_pos_y)
    update_maze(g_player_pos_x, g_player_pos_y)
    
def on_touch_left(pressed):
  global g_maze_index
  if pressed and g_maze_index > 0:
    g_maze_index = g_maze_index - 1
    init_maze()
    refresh_display(g_player_pos_x, g_player_pos_y)

def on_touch_right(pressed):
  global g_maze_index
  if pressed and g_maze_index < 1:
    g_maze_index = g_maze_index + 1
    init_maze()
    refresh_display(g_player_pos_x, g_player_pos_y)


# Initialize application
init_app()
init_maze()
refresh_display(g_player_pos_x, g_player_pos_y)

# Start animation timers
virtualtimers.begin(50)
virtualtimers.new(0, animate, False)

# Add key and touchpad handlers
touchpads.on(touchpads.LEFT, on_touch_left)
touchpads.on(touchpads.RIGHT, on_touch_right)
keypad.add_handler(on_key)