C实例No.13|2048小游戏的实现


把完整代码复制到test.c文件里然后在Linux虚拟机ubuntu执行以下就可以愉快的玩耍了。

gcc -o test test.c
./test

内容呈现

在这里插入图片描述

代码


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <termios.h>

#define SIZE    16
#define UP      0x69   // 小写 i
#define UP_B    0x49   // 大写 I
#define DOWN    0x6b   // 小写 k
#define DOWN_B  0x4b   // 大写 K
#define RIGHT   0x6c   // 小写 l
#define RIGHT_B 0x4c   // 大写 L
#define LEFT    0x6a   // 小写 j
#define LEFT_B  0x4a   // 大写 J

int arr[SIZE] = {0};
int is_move = 0;       // 这个标志是判断是否有移动过没有
int is_merge = 0;      // 这个标志是判断是否有合并过没有
int max;               // 当前最大数字
int total;             // 统计总分数


static struct termios oldt;

void restore_terminal_settings(void)
{
	tcsetattr(0, TCSANOW, &oldt);
}

void disable_terminal_return(void)
{
	struct termios newt;
	tcgetattr(0, &oldt);
	newt = oldt;
	newt.c_lflag &= ~(ICANON | ECHO);
	//设置输入的字符不显示
	tcsetattr(0, TCSANOW, &newt);
	//销毁之前的设置
	atexit(restore_terminal_settings);
}

/* init
   初始化数组,产生两个不同的随机数,从0-15产生,对应数组下标,然后把该下标数组的值设置为2

   +-------------  2048  --------------+
   +-----------------------------------+
   |        |        |        |        |
   |       2|        |        |        |
   |--------+--------+--------+--------|
   |        |        |        |        |
   |        |       2|        |        |
   |--------+--------+--------+--------|
   |        |        |        |        |
   |        |        |        |        |
   |--------+--------+--------+--------|
   |        |        |        |        |
   |        |        |        |        |
   +-----------------------------------+


*/
void init(void)
{
	int i, random1, random2;
	random1 = rand() % SIZE;
	for(i = 0; i < 1; i++)
	{
		random2 = rand() % SIZE;
		if(random1 == random2)
		{
			i--;
		}
	}
	arr[random1] = 2;
	arr[random2] = 2;
}

//判断是否不能移动并且合并,就是game over了
int is_dead()
{
	int i, j;
	for(i = 0; i < SIZE; i++)
	{
		if(!arr[i])  //如果数组当中还有0,说明还能移动,返回0,说明游戏还没结束呢
		{
			return 0;
		}
	}
	/*到这里说明,的确在一个方向不能移动了,但是,有可能换个方向还可以移动,
	 * 所以能到这里说明,16个格子全部有数字,都不为0,接下来判断4行,4列
	 *每相邻两个数字,是否有相同的如果有说明通过移动还可以动,这时候还没死
	 */
	for(i = 0; i < SIZE; i += 4)
	{
		for(j = i; j < i + 3; j++)
		{
			if(arr[j] == arr[j + 1])  //这里就是判断4行,一行一行看,每相邻两个格子如果数字有相同的,return 0,说明游戏还可以继续
			{
				return 0;
			}
		}
	}
	for(i = 0; i < 4; i++)
	{
		for(j = i; j < i + 12; j += 4)
		{
			if(arr[j] == arr[j + 4])  //这里判断4列,一列一列看,每相邻两个格子如果有数字相同的,return 0,说明游戏还可以继续
			{
				return 0;
			}
		}
	}
	return 1;    // 能到这里,说明16个格子全部都有数字,都不为0,而且各个方向无论怎么移动都不能合并,那么游戏结束game over,return 1
}

//  这个函数返回当前最大的数字
int max_num(void)
{
	int i;
	static int count = 0;
	for(i = 0; i < SIZE; i++)
	{
		if(arr[i] > max)
		{
			max = arr[i];  //找最大数
		}
	}
	if(!count && 2048 == max)  //判断如果最大数==2048,过关,按任意键继续玩
	{
		count++;
		printf("恭喜过关,请按任意键继续!");
		getchar();
	}
	return max;
}

//这个函数,负责每次打印图形,每次移动操作都会修改数组,改一次,打印一次
void print_game(void)
{
	system("clear");
	int i;
	printf("按【I】向上移动\n");
	printf("按【K】向下移动\n");
	printf("按【J】向左移动\n");
	printf("按【L】向右移动\n");
	printf("按【Ctrl + Z】退出游戏\n");
	printf("最大的数是:%d\n总分是:%d\n", max_num(), total);
	printf("\n");
	printf("+-------------  2048  --------------+\n");
	printf("+-----------------------------------+\n");
	printf("|        |        |        |        |\n");
	for(i = 0; i < SIZE; i++)
	{
		if(0 == i || 4 == i || 8 == i || 12 == i)
		{
			printf("|");
		}
		printf(0 == arr[i] ? "%9c|" : "%8d|", arr[i]);  //这里有个对数组中数字0的处理,如果是0 , 那么按%c格式打印是空白,好看些
		//一行结束之后打印下面的内容
		if(3 == i || 7 == i || 11 == i)
		{
			printf("\n");
			printf("|--------+--------+--------+--------|\n");
			printf("|        |        |        |        |\n");
		}
	}
	printf("\n");
	printf("+-----------------------------------+\n");
	printf("\n\n\n");
	if(is_dead()) //如果能进这个if,说明game over,然后按任意键重新玩,
	{
		int i;
		printf("game over!\n");
		printf("请按任意键重新开始!\n");
		getchar();
		for(i = 0; i < SIZE; i++)   //数组清0
		{
			arr[i] = 0;
		}
		init();  //重新初始化
		print_game();
	}
}

//这个函数是每次移动这个动作完成时,在剩下的格子中产生一个随机数字,对应数组下标,然后把该下标数组值设置为2,即每次移动完在剩下空白的地方出现一个2
void rand_num(void)
{
	while(is_move || is_merge)  //如果可以移动,或者可以合并,那么才能产生数字,
	{
		int random = rand() % SIZE;
		if(!arr[random])         //这个就是保证产生的随机数是空白中的,已经格子中已经有的就不行,重新产生
		{
			arr[random] = 2;
			break;
		}
	}
	is_move = 0;
	is_merge = 0;
}

/*
 *功能:移动
 *参数:loop_count:需要循环的次数
		current_i: 当前移动的元素数组下标
        direction:  移动方向上-4、下+4、左-1、右+1
*/

//比如现在当前的i是5 ,即第二行第2个数字2,需要向上移动,那么它的循环次数是1次,direction=-4,说明它上面的那个数字下标比当前这个小4

void move_go(int loop_count, int current_i, int direction)
{
	int i;
	for(i = 0; i < loop_count; i++)
	{
		//if(arr[current_i] && !arr[current_i + direction])  如果当前这个数字不为0,并且它上面的那个数字为0那么就可以向上移动

		if(arr[current_i] && !arr[current_i + direction])
		{
			arr[current_i + direction] = arr[current_i];  //把它上面那个数字改成当前这个数字,
			arr[current_i] = 0;  //把当前这个数字改成0
			current_i += direction;  //再把当前的current_i ,-4,继续看它上面是什么情况,能移动就移动不能移动就算了
			is_move = 1;   //能进来说明能移动,所以把标志 是否能移动 设置为1
		}
	}
}


void move_up(void)
{
	/*
	   +-------------	2048  --------------+
	   +-----------------------------------+
	   |		 |		  | 	   |		|
	   |		2|		  | 	   |		|
	   |--------+--------+--------+--------|
	   |		 |		  | 	   |		|
	   |		 |		 2| 	   |		|
	   |--------+--------+--------+--------|
	   |		 |		  | 	   |		|
	   |		 |		  | 	   |		|
	   |--------+--------+--------+--------|
	   |		 |		  | 	   |		|
	   |		 |		  | 	   |		|
	   +-----------------------------------+
	   */
	// loop_count循环次数,为啥会有这个变量,因为向上移动有的数字需要移动一次,有的需要移动2,3次,而最上边的数字则不需要移动所以,
	//loop_count控制循环次数,direction,控制方向,向上移动所以是以从下往上的角度看的,那么direction=-4,意思就是上一个元素的下标比当前元素小4
	//所以是-4.
	int i, loop_count, direction;
	for(i = 0; i < SIZE; i++)
	{
		if(arr[i])  //移动时,如果这个格子的数字不为0才移动,为0的话不用管了,能进到这个if说明当前要移动的数字不为0,也就是不是空白,空白不需要移动
		{
			loop_count = i / 4;
			//计算循环次数,0 1 2 3,是最上层的数字不用移动,所以i /4=0,不用移动,第二行4,5,6,7,最多需要移动1次,i/4=1,依次类推
			direction = -4;
			//把当前格子中的数组下标,需要循环的次数,还有方向传给move_go 这个函数,接下来去看move_go 这个函数
			move_go(loop_count, i, direction);
		}
	}
}


void move_down(void)
{
	int i, loop_count, direction;
	for(i = SIZE - 1; i >= 0; i--)
	{
		if(arr[i])
		{
			loop_count = (4 - 1) - i / 4;
			direction = 4;
			move_go(loop_count, i, direction);
		}
	}
}


void move_right(void)
{
	int i, loop_count, direction;
	for(i = SIZE - 1; i >= 0; i--)
	{
		if(arr[i])
		{
			loop_count = (4 - 1) - i % 4;
			direction = 1;
			move_go(loop_count, i, direction);
		}
	}
}


void move_left(void)
{
	int i, loop_count, direction;
	for(i = 0; i < SIZE; i++)
	{
		if(arr[i])
		{
			loop_count = i % 4;
			direction = -1;
			move_go(loop_count, i, direction);
		}
	}
}

//合并函数,只负责一次合并,把接收当前数字下标,把它上或者下或者左或者右的格子合并一个
void merge(int current_i, int direction)
{
	//如果当前这个格子和它四个方向相邻的格子都不为0时并且,这两个数字相等时才进行合并操作
	if(arr[current_i] && arr[current_i + direction] && arr[current_i] == arr[current_i + direction])
	{
		//计算合并的结果
		arr[current_i] = arr[current_i + direction] * 2;
		//计算总分
		total += arr[current_i];
		arr[current_i + direction] = 0;
		is_merge = 1;
	}
}

void move_up_pre(void)
{
	move_up();
	int i, j, direction = 4; //合并是从上往下看,所以direction=4,即它下面的那个格子数组下标比当前这个大4
	for(i = 0; i < 4; i++)
		// 这里的i从0 到3 ,即0,1,2,3,控制列数,即0开头的那一列,1开头的那一列,以此类推
	{
		for(j = i; j < i + 12; j += 4)
			//  当i=0时,j 是0,4,8,当i=1时,j是1,5,9以此类推,传给merge进行合并,关于下、左、右合并道理类似,我就不说了
		{
			merge(j, direction);
		}
	}
	move_up();
}

void move_down_pre(void)
{
	move_down();
	int i, j, direction = -4;
	for(i = 4 - 1; i >= 0; i--)
	{
		for(j = i + 12; j >= 4; j -= 4)
		{
			merge(j, direction);
		}
	}
	move_down();
}

void move_right_pre(void)
{
	move_right();
	int i, j, direction = -1;
	for(i = 4 - 1; i >= 0; i--)
	{
		for(j = 4 * i + 3; j > 4 * i; j--)
		{
			merge(j, direction);
		}
	}
	move_right();
}

void move_left_pre(void)
{
	move_left();
	int i, j, direction = 1;
	for(i = 0; i <= 3; i++)
	{
		for(j = 4 * i; j < 4 * i + 3; j++)
		{
			merge(j, direction);
		}
	}
	move_left();
}

int main(void)
{
	srand(time(NULL));
	init();
	print_game();
	disable_terminal_return();
	while(1)
	{
		switch(getchar())
		{
			case UP:
			case UP_B:
				move_up_pre();
				rand_num();
				print_game();
				break;
			case DOWN:
			case DOWN_B:
				move_down_pre();
				rand_num();
				print_game();
				break;
			case RIGHT:
			case RIGHT_B:
				move_right_pre();
				rand_num();
				print_game();
				break;
			case LEFT:
			case LEFT_B:
				move_left_pre();
				rand_num();
				print_game();
				break;
			default:
				break;
		}
	}
	return 0;
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值