/*
* 2048 Game
* 
* This file requires terminalmode2.h or terminalmode2_mod.h to build, but due to copyright restrictions, it cannot be distributed.
*
* NOTICE: Using this file as a base for your game will be considered cheating. Absolutely do not do it.
*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define N			4		/* マップサイズ */
#define WIN_TILE	4096	/* このタイルが出たらクリア */
#define SPAWN_4_MOD	10		/* rand() % この値 */
#define EMPTY		0		/* 空きマスの値 */
#define RESET_COLOR	"\033[0m"

int board[N][N];
int move_count;

/* ===== タイルの色テーブル =====
* 1:2
* 2:4
* 3:8...
*/
char *tile_colors[] = {
	"\033[0m",						//一般
	"\033[48;5;250m\033[38;5;232m",	//2
	"\033[48;5;226m\033[38;5;232m",	//4
	"\033[48;5;220m\033[38;5;232m",	//8
	"\033[48;5;214m\033[38;5;232m",	//16
	"\033[48;5;208m\033[38;5;232m",	//32
	"\033[48;5;202m\033[38;5;232m",	//64
	"\033[48;5;196m\033[38;5;232m",	//128
	"\033[48;5;160m\033[38;5;232m",	//256
	"\033[48;5;124m\033[38;5;232m",	//512
	"\033[48;5;92m\033[38;5;232m",	//1024
	"\033[48;5;88m\033[38;5;232m",	//2048
	"\033[48;5;201m\033[38;5;255m"	//4096
};

#define TILE_COLORS_MAX 11

void init_board(void);
int color_index(int v);
void draw_board(void);

int  slide_and_merge_row(int row[N]);

int move_left(void);
int move_right(void);
int move_up(void);
int move_down(void);

int add_random_tile(void);
int can_move(void);
int current_max_tile(void);
int read_key(void);

void init_board(void)
{
	int i, j;
	for (i = 0; i < N; ++i){
		for (j = 0; j < N; ++j){
			board[i][j] = EMPTY;//0
		}
	}

	/*乱数*/
	srand((unsigned int)time(NULL));

	move_count = 0;

	/* タイルx4 */
	add_random_tile();
	add_random_tile();
	add_random_tile();
	add_random_tile();
}

int color_index(int v)
{
	int color_idx;
	if (v <= 0) return 0;
	color_idx = 0;
	while (v > 1) {
		v = v / 2;		//数
		color_idx++;
	}
	if (color_idx > TILE_COLORS_MAX){
		color_idx = TILE_COLORS_MAX;
	}
	return color_idx;
}

void draw_board(void)
{
	int i, j, v, color_idx;

	printf("\033[2J\033[H");

	printf("      mmmm   mmmm     mm   mmmm           mmm %c%c", 0x0A, 0x0D);
	printf("    +   +# m+  +m   m+#  #    #        m+   +%c%c", 0x0A, 0x0D);
	printf("       m+ #  m #  #+ #  +mmmm+        #     %c%c", 0x0A, 0x0D);
	printf("    m+   #    # #mmm#m #   +#        #     %c%c", 0x0A, 0x0D);
	printf(" m#mmmm  #mm#      #  +#mmm+   #     +mmm+%c%c%c%c", 0x0A, 0x0D, 0x0A, 0x0D);
	printf("[w/a/s/d : move /  q: Exit]%c%c", 0x0A, 0x0D); //0x0A,0x0Dは\nの代わり、terminalmodeより
	printf("+---------+---------+---------+---------+%c%c", 0x0A, 0x0D);

	for (i = 0; i < N; ++i) {
		for (j = 0; j < N; ++j) {
			v  = board[i][j];
			color_idx = color_index(v);
			printf("|");
			if (v == EMPTY) {
				printf("         ");
			} else {
				printf(" %s%6d%s  ", tile_colors[color_idx], v, RESET_COLOR);
			}
		}
		printf("|%c%c", 0x0A, 0x0D);
		printf("+---------+---------+---------+---------+%c%c", 0x0A, 0x0D);
	}
	printf("%c%cMoves: %d%c%c", 0x0A, 0x0D, move_count, 0x0A, 0x0D);
}

/*
*0を飛ばして数字を左に詰める	[0,2,0,2] → tmp=[2,2,0,0]
*隣り合う同じ数字を合体する		tmp=[2,2,0,0] → [4,0,0,0]
*再び左に詰めて row に書き戻す
*処理前_orig と比較して変化があれば 1 を返す
*
*/
int slide_and_merge_row(int row[N])
{
	int orig[N];
	int tmp[N];
	int i, pos, changed;

	for (i = 0; i < N; ++i) {
		orig[i] = row[i];
		tmp[i]  = EMPTY;
	}

	pos = 0;
	for (i = 0; i < N; ++i) {
		if (row[i] != EMPTY) {
			tmp[pos] = row[i];
			pos++;
		}
	}

	for (i = 0; i < N - 1; ++i) {
		if (tmp[i] != EMPTY && tmp[i] == tmp[i + 1]) {
			tmp[i]    *= 2;		//*= はnをかけて代入
			tmp[i + 1] = EMPTY;
		}
	}

	pos = 0;
	for (i = 0; i < N; ++i) {
		if (tmp[i] != EMPTY) {
			row[pos] = tmp[i];
			pos++;
		}
	}
	for (i = pos; i < N; ++i) row[i] = EMPTY;

	changed = 0;
	for (i = 0; i < N; ++i) {
		if (row[i] != orig[i]) {
			changed = 1;
		}
	}
	return changed;
}

int move_left(void)
{
	int i, j, changed, total;
	int row[N];
	total = 0;
	for (i = 0; i < N; ++i) {
		for (j = 0; j < N; ++j) row[j] = board[i][j];
		changed = slide_and_merge_row(row);
		if (changed) {
			for (j = 0; j < N; ++j) board[i][j] = row[j];
			total = 1;
		}
	}
	return total;
}

int move_right(void)
{
	int i, j, changed, total;
	int row[N];
	total = 0;
	for (i = 0; i < N; ++i) {
		for (j = 0; j < N; ++j) row[j] = board[i][N - 1 - j];
		changed = slide_and_merge_row(row);
		if (changed) {
			for (j = 0; j < N; ++j) board[i][N - 1 - j] = row[j];
			total = 1;
		}
	}
	return total;
}

int move_up(void)
{
	int c, r, changed, total;
	int col[N];
	total = 0;
	for (c = 0; c < N; c++) {
		for (r = 0; r < N; r++) col[r] = board[r][c];
		changed = slide_and_merge_row(col);
		if (changed) {
			for (r = 0; r < N; r++) board[r][c] = col[r];
			total = 1;
		}
	}
	return total;
}

int move_down(void)
{
	int c, r, changed, total;
	int col[N];
	total = 0;
	for (c = 0; c < N; c++) {
		for (r = 0; r < N; r++) col[r] = board[N - 1 - r][c];
		changed = slide_and_merge_row(col);
		if (changed) {
			for (r = 0; r < N; r++) board[N - 1 - r][c] = col[r];
			total = 1;
		}
	}
	return total;
}

int add_random_tile(void)
{
	int empties, empty_index, i, j;

	empties = 0;
	for (i = 0; i < N; ++i){
		for (j = 0; j < N; ++j)
		if (board[i][j] == EMPTY) empties++;
	}

	if (empties == 0) return 0;

	empty_index = rand() % empties;

	for (i = 0; i < N; ++i) {
		for (j = 0; j < N; ++j) {
			if (board[i][j] == EMPTY) {
				if (empty_index == 0) {
//					if (rand() % SPAWN_4_MOD == 0) {
//						board[i][j] = 4;
//					}else{
//						board[i][j] = 2;
//					}
					board[i][j] = (rand() % SPAWN_4_MOD == 0) ? 4 : 2;
					return 1;
				}
				empty_index--;
			}
		}
	}
	return 0;
}

//動けるか
int can_move(void)
{
	int i, j;
	for (i = 0; i < N; ++i) {
		for (j = 0; j < N; ++j) {
			if (board[i][j] == EMPTY) return 1;
			if (j + 1 < N && board[i][j] == board[i][j + 1]) return 1;
			if (i + 1 < N && board[i][j] == board[i + 1][j]) return 1;
		}
	}
	return 0;
}

//現在存在する一番でかいやつ
int current_max_tile(void)
{
	int i, j, max;
	max = 0;
	for (i = 0; i < N; ++i)
		for (j = 0; j < N; ++j)
		if (board[i][j] > max) max = board[i][j];
	return max;
}

/*
* getkbchar()（terminalmode2_mod.h）より、
* 矢印キーは ESC(27) + '[' + 方向文字 の3バイトで届く
*/
int read_key(void)
{
	int c, c2, c3;
	c = (int)getkbchar();
	if (c == 27) {
		c2 = (int)getkbchar();
		if (c2 == '[') {
			c3 = (int)getkbchar();
			if (c3 == 'A') return 'w';   /* ↑ */
			if (c3 == 'B') return 's';   /* ↓ */
			if (c3 == 'C') return 'd';   /* → */
			if (c3 == 'D') return 'a';   /* ← */
		}
		return 0;
	}
	return c;
}

int main(void)
{
	int c, moved, max_tile;

	/* tmode2_modでRAWへ */
	set_terminal_reset();

	init_board();
	draw_board();

	while (1) {
		c = read_key();

		if (c == 0) continue;
		if (c == 'q' || c == 'Q') break;

		moved = 0;
		if      (c == 'a'){
			moved = move_left();
		}else if (c == 'd'){
			moved = move_right();
		}else if (c == 'w'){
			moved = move_up();
		}else if (c == 's'){
			moved = move_down();
		}

		if (moved) {
			move_count++;
			add_random_tile();
			draw_board();

			max_tile = current_max_tile();

			if (max_tile >= WIN_TILE) {
				printf("Congratulations!%c%c", 0x0A, 0x0D);
				printf("You created %d  -  WIN!%c%c", max_tile, 0x0A, 0x0D);
				break;
			}

			if (!can_move()) {
				printf("Game Over!%c%c", 0x0A, 0x0D);
				printf("Max tile: %d%c%c", max_tile, 0x0A, 0x0D);
				break;
			}
		}
	}

	return 0;
}
