import random
import pygame
from pygame.locals import *
pygame.init()

class Board(object):
    def __init__(self, image_file, dim):
        self.image = pygame.image.load(image_file)
        tile_size = self.image.get_width()/dim	
	
        all_tiles = [] #a list of references to all tiles, for simplicity later
	for tile_y_coord in range(dim):
            for tile_x_coord in range(dim):
                if tile_y_coord==dim-1 and tile_x_coord==dim-1: #if bottom corner, make it blank
		    tile = Tile(tile_x_coord, tile_y_coord, self.image, tile_size, True)
		    self.blank = tile
		else:
		    tile = Tile(tile_x_coord, tile_y_coord, self.image, tile_size)
		all_tiles.append(tile)
		
	#We will shuffle the references to the Tiles in the 1D list of all Tiles
	random.shuffle(all_tiles)

        self.grid = [] #a list of rows, which are themselves lists of Tiles (2D list)
	self.group = pygame.sprite.Group()
        for y in range(dim):
            row = []
            for x in range(dim):
                a_tile = all_tiles.pop(0) #remove the first Tile in shuffled list
                a_tile.set_coords((x,y))
		self.group.add(a_tile) #and add it to the Group of Tiles 
		row.append(a_tile)
				
	    #with row complete, add row to the grid
	    self.grid.append(row)

        #start the selection square in the top-corner.
	self.select = Select(tile_size, dim)
        self.select.set_coords(0,0)
        

    def get_size(self):
        return (self.image.get_width(), self.image.get_height())

    def check_win(self):
	win = True
	for tile_y in range(len(self.grid)):
            for tile_x in range(len(self.grid)):
		check_pos = (tile_x, tile_y)
		#boolean logic will only remain True if all pieces are in right spot
		win = win and check_pos == self.grid[tile_y][tile_x].solution_pos
	return win
    

    def __str__(self):
	out = ""
	for tile_y in range(len(self.grid)):
            for tile_x in range(len(self.grid)):
		out += "%s " %str(self.grid[tile_y][tile_x].get_coords())
	    out += "\n"
	out += "blank: " + str(self.blank.get_coords()) + "\n"
	return out

    def tile_swap(self):
	current = self.select.get_coords()
	blank_pos = self.blank.get_coords()
	dist = ((current[0]-blank_pos[0])**2 + (current[1]-blank_pos[1])**2)**0.5
	#if Cartesian distance is 1, then we can do the swap.  Nice!
	if 0.1 < dist < 1.1: #accounts for floating point error
	    #perform the necessary swaps of coordinates
	    current_tile = self.grid[current[1]][current[0]]
	    blank_tile = self.grid[blank_pos[1]][blank_pos[0]]

	    #swap in our storage grid
	    self.grid[current[1]][current[0]] = blank_tile
	    self.grid[blank_pos[1]][blank_pos[0]] = current_tile

	    #swap Tile coordinate properties
	    current_tile.set_coords(blank_pos)
	    blank_tile.set_coords(current)
	    


class Tile(pygame.sprite.Sprite):
    def __init__(self, x, y, master_image, size, is_blank=False):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((size,size))
	if is_blank: #one of these per puzzle
	    #fill blank as grey
	    self.image = pygame.Surface((size, size))
	    self.image.fill((100,100,100))
	else:
	    self.image.blit(master_image, (0,0), (size*x, size*y, size*(x+1), size*(y+1)))
        self.rect = self.image.get_rect()
	self.solution_pos = (x,y)
	self.is_blank = is_blank
        
    def get_coords(self):
        return (self.rect.left/self.rect.width, self.rect.top/self.rect.height)
        
    def set_coords(self, coords):
        self.rect.top = coords[1]*self.rect.height
        self.rect.left= coords[0]*self.rect.width

	
class Select(pygame.sprite.Sprite):
    def __init__(self, size, dim):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.Surface((size,size))
	self.image.fill((255,255,0))          #make a yellow square
	mask = pygame.Surface((size-6,size-6)) 
	mask.fill((0,0,0))                    #make a smaller black square inside
        self.image.blit(mask, (3,3))
        self.image.set_colorkey((0,0,0))      #then make the black transparent
        self.rect = self.image.get_rect()     #leaving the outer yellow square
	self.dim = dim

    def set_coords(self, x, y):
        self.rect.top = y*self.rect.height
        self.rect.left = x*self.rect.width
        
    def get_coords(self):
        return (self.rect.left/self.rect.width, self.rect.top/self.rect.height)

    def move_left(self):
        if self.rect.left > 0:
	    self.rect.left -= self.image.get_width()	
    def move_right(self):
        if self.rect.right < self.dim*self.image.get_width()-1:
	    self.rect.left += self.image.get_width()
    def move_up(self):
        if self.rect.top > 0:
	    self.rect.top -= self.image.get_height()
    def move_down(self):
        if self.rect.bottom < self.dim*self.image.get_height()-1:
	    self.rect.top += self.image.get_height()


#------- START OF PROGRAM HERE -------------------------------------------------
pygame.joystick.init()
try:
    j = pygame.joystick.Joystick(0) # create a joystick instance
    j.init() # init instance
except pygame.error:
    pass

dim = 4
board = Board("lorikeet_square.bmp", dim)
square_size = board.get_size()[0]/dim

screen = pygame.display.set_mode(board.get_size())

background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((255, 255, 255))
  
clock = pygame.time.Clock()
keepGoing = True
while keepGoing:
    clock.tick(30)
    
    for ev in pygame.event.get():
	if ev.type == QUIT:
	    keepGoing = False
	#Keyboard 
	elif ev.type == KEYDOWN:
	    if ev.key == K_RIGHT:
		board.select.move_right()
	    elif ev.key == K_LEFT:
		board.select.move_left()
	    elif ev.key == K_UP:
		board.select.move_up()
	    elif ev.key == K_DOWN:
		board.select.move_down()
	    elif ev.key == K_RETURN:
		board.tile_swap()
		if board.check_win():
		    keepGoing = False
		
	#or Joystick!
	elif ev.type == JOYHATMOTION:
	    if ev.value == (-1, 0) :
		board.select.move_left()
	    elif ev.value == (1, 0) :
		board.select.move_right()
	    elif ev.value == (0, 1) :
		board.select.move_up()
	    elif ev.value == (0, -1) :
		board.select.move_down()
	elif ev.type == JOYBUTTONDOWN and ev.button == 0:
	    #just the 'A' button on the controller
	    board.tile_swap()
	    if board.check_win():
		keepGoing = False
    
    #We can easily re-draw the whole display here.
    #See DirtySprites for a nicer way to just worry about the ones that have changed
    board.group.update()
    board.group.draw(screen)
    screen.blit(board.select.image, board.select.rect.topleft) #put box on top
    pygame.display.flip()

#if we got out of the loop by winning, show a congrats message    
if board.check_win():
    my_font = pygame.font.SysFont("comicsansms", 40, bold=True)
    label = my_font.render("CONGRATS!", True, (255,255,255))
    label_rect = label.get_rect()
    label_rect.center = board.image.get_rect().center
    screen.blit(label, (label_rect))
    pygame.display.flip()
    keepGoing = True
    while keepGoing:
	clock.tick(30)
	for ev in pygame.event.get():
	    if ev.type == QUIT:
		keepGoing = False
    
