#include <curses.h>

#define STARTX 9
#define STARTY 3
#define WIDTH  6
#define HEIGHT 4

#define BLANK 0

typedef struct _tile {
	int x;
	int y;
}tile;

void init_board(int **board, int n, tile *blank);
void board(WINDOW *win, int starty, int startx, int lines, int cols, 
	   int tile_width, int tile_height);
void shuffle_board(int **board, int n);
void move_blank(int direction, int **s_board, int n, tile *blank);
int check_win(int **s_board, int n, tile *blank);

enum { LEFT, RIGHT, UP, DOWN };

int main(int argc, char *argv[])
{	int **s_board;
	int n, i, ch;
	tile blank;

	if(argc != 2)
	{	printf("Usage: %s <shuffle board order>\n", argv[0]);
		exit(1);
	}
	n = atoi(argv[1]);
	
	s_board = (int **)calloc(n, sizeof(int *));
	for(i = 0;i < n; ++i)
		s_board[i] = (int *)calloc(n, sizeof(int));
	init_board(s_board, n, &blank);
	initscr();
	keypad(stdscr, TRUE);
	cbreak();
	shuffle_board(s_board, n);
	while((ch = getch()) != KEY_F(1))
	{	switch(ch)
		{	case KEY_LEFT:
				move_blank(RIGHT, s_board, n, &blank);
				break;
			case KEY_RIGHT:
				move_blank(LEFT, s_board, n, &blank);
				break;
			case KEY_UP:
				move_blank(DOWN, s_board, n, &blank);
				break;
			case KEY_DOWN:
				move_blank(UP, s_board, n, &blank);
				break;
		}
		shuffle_board(s_board, n);
		if(check_win(s_board, n, &blank) == TRUE)
		{	mvprintw(24, 0, "You Win !!!\n");
			refresh();
			break;
		}
	}
	endwin();
	return 0;	
}

void move_blank(int direction, int **s_board, int n, tile *blank)
{	int temp;

	switch(direction)
	{	case LEFT:
		{	if(blank->x != 0)
			{	--blank->x;
				temp = s_board[blank->x][blank->y];
				s_board[blank->x + 1][blank->y] = temp;
				s_board[blank->x][blank->y] = BLANK;
			}
		}
		break;
		case RIGHT:
		{	if(blank->x != n - 1)
			{	++blank->x;
				temp = s_board[blank->x][blank->y];
				s_board[blank->x - 1][blank->y] = temp;
				s_board[blank->x][blank->y] = BLANK;
			}
		}
		break;
		case UP:
		{	if(blank->y != 0)
			{	--blank->y;
				temp = s_board[blank->x][blank->y];
				s_board[blank->x][blank->y + 1] = temp;
				s_board[blank->x][blank->y] = BLANK;
			}
		}
		break;
		case DOWN:
		{	if(blank->y != n - 1)
			{	++blank->y;
				temp = s_board[blank->x][blank->y];
				s_board[blank->x][blank->y - 1] = temp;
				s_board[blank->x][blank->y] = BLANK;
			}
		}
		break;			
	}
}

int check_win(int **s_board, int n, tile *blank)
{	int i, j;

	s_board[blank->x][blank->y] = n * n;
	for(i = 0;i < n; ++i)
		for(j = 0;j < n; ++j)
			if(s_board[i][j] != j * n + i + 1)
			{	s_board[blank->x][blank->y] = BLANK;
				return FALSE;
			}
	
	s_board[blank->x][blank->y] = BLANK;
	return TRUE;	
}

void init_board(int **s_board, int n, tile *blank)
{	int i, j, k;
	int *temp_board;

	temp_board = (int *)calloc(n * n, sizeof(int));
	srand(time(NULL));
	for(i = 0;i < n * n; ++i)
	{    
repeat :
		k = rand() % (n * n);
		for(j = 0;j <= i - 1; ++j)
			if (k == temp_board[j])
				goto repeat;
			else
				temp_board[i] = k;
	}
	k = 0;
	for (i = 0;i < n;++i)
		for(j = 0;j < n; ++j,++k)
		{	if(temp_board[k] == 0)
			{	blank->x = i;
				blank->y = j;
			}
			s_board[i][j] = temp_board[k];
		}
	free(temp_board);
}

void board(WINDOW *win, int starty, int startx, int lines, int cols, 
	   int tile_width, int tile_height)
{	int endy, endx, i, j;
	
	endy = starty + lines * tile_height;
	endx = startx + cols  * tile_width;
	
	for(j = starty; j <= endy; j += tile_height)
		for(i = startx; i <= endx; ++i)
			mvwaddch(win, j, i, ACS_HLINE);
	for(i = startx; i <= endx; i += tile_width)
		for(j = starty; j <= endy; ++j)
			mvwaddch(win, j, i, ACS_VLINE);
	mvwaddch(win, starty, startx, ACS_ULCORNER);
	mvwaddch(win, endy, startx, ACS_LLCORNER);
	mvwaddch(win, starty, endx, ACS_URCORNER);
	mvwaddch(win, 	endy, endx, ACS_LRCORNER);
	for(j = starty + tile_height; j <= endy - tile_height; j += tile_height)
	{	mvwaddch(win, j, startx, ACS_LTEE);
		mvwaddch(win, j, endx, ACS_RTEE);	
		for(i = startx + tile_width; i <= endx - tile_width; i += tile_width)
			mvwaddch(win, j, i, ACS_PLUS);
	}
	for(i = startx + tile_width; i <= endx - tile_width; i += tile_width)
	{	mvwaddch(win, starty, i, ACS_TTEE);
		mvwaddch(win, endy, i, ACS_BTEE);
	}
	wrefresh(win);
}

void shuffle_board(int **s_board, int n)
{	int i,j, deltax, deltay;
	int startx, starty;

	starty = (LINES - n * HEIGHT) / 2;
	startx = (COLS  - n * WIDTH) / 2;
	clear();
	mvprintw(24, 0, "Press F1 to Exit");
	board(stdscr, starty, startx, n, n, WIDTH, HEIGHT);
	deltay = HEIGHT / 2;
	deltax = WIDTH  / 2;
	for(j = 0; j < n; ++j)
		for(i = 0;i < n; ++i)
			if(s_board[i][j] != BLANK)
				mvprintw(starty + j * HEIGHT + deltay,
					 startx + i * WIDTH  + deltax,
					 "%-2d", s_board[i][j]);
	refresh();
}
	

