• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C语言——贪吃蛇的实现

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

贪吃蛇的实现

一、作用域和生命周期

  • 以开房为例子举例(变量)

    开房:给了房费(malloc函数或者定义变量),分配一个房间(空间)

    作用域:在某个特定的房间(空间),起作用的范围

    • 局部变量的作用域:在某个{}里面

    生命周期:退房了(free或者系统自行释放),不能再使用这个空间

    • 局部变量离开了{},无法在使用
  • 各种变量

    普通局部变量:(int a;)

    • 通常放在某个函数、某个语句、某个{}里面
    • 程序执行到定义语句时,才会给a分配空间
    • 离开{},a自动回收、释放。所谓释放,不是空间消失,而是这个空间用户不能再用
    • 局部变量,不初始化,值为垃圾数据,随机数,不同编译器的至不一样

    1)系统维护内存,自动分配空间,自动释放,自动变量

    static局部变量:(static int a;)

    • main调用之前,就已经分配空间,就已经初始化
    • 整个程序结束后,static局部变量才会释放
    • static局部变量只能用常量初始化
    • 还叫局部变量,站在作用域角度看,该变量只能在{}内部直接使用

    1)在main函数调用前,已经分配空间,已经初始化,不人为初始化,自动赋值为0

    2)整个程序结束后,static局部变量才会释放

    普通全局变量:(int a;)

    1)真正的全局,所有地方都能使用的变量

    • 定义在函数外部的变量就是全局变量

    • 全局变量,任何地方都能使用这个变量

    • 使用这个全局变量时,如果定义不在前面,需要声明

    • 所谓的声明,告诉编译器,这个变量有定义的,只是放在别的地方定义

      extern int a;//声明不能赋值	`extern`:1.用于全局变量声明 2.用于全局函数声明
      
    • 可以在定义时同时初始化,在函数外部,不能单独给全局变量赋值

    2)在main函数调用前,已经分配空间,已经初始化,不人为初始化,自动赋值为0

    3)整个程序结束后,普通变量才会释放

    4)只能定义一次,可以声明多次

    1. 声明和定义(包括多文件)

      • 全局变量或全局函数,可以声明多次,但是只能定义一次
      • extern只是声明,没有定义,没有定义就无法赋值(声明不能赋值,不建立存储空间)
      • 建议:定义全局变量要赋值,声明时,加extern
      int a;       //定义
      extern int a;//声明
      

    static全局变量:(static int a;)

    1)针对某个文件的全局变量

    • main函数调用之前,就已经分配空间,整个程序结束,才释放
    • static全局变量,在每个文件可以定义一次,没有声明一说

    2)在main函数调用前,已经分配空间,已经初始化,不人为初始化,自动赋值为0

    3)定义在函数外部

二、内存分区

​ 程序:通过gcc编译器,翻译成可执行程序(机器能识别的二进制代码),这个可执行程序,可以提前规划内存布局,没有加载内存:

  • text(代码区):程序入口地址,函数

  • date:初始化全局变量,static变量,可读可写

    ​ 只读区,文字常量区,放字符串常量,例:"aaa"

  • bss:没有初始化的全局变量或static变量(用户没有指定初始化的值),在main函数前,由系统初始化为0

​ 把可执行程序运行起来,它就成为进程,他需要内存,由系统加载:(运行时才有)

  • stack(栈区):
    • 普通局部变量
  • heap(堆区):
    • 通过用户申请和释放空间,mallocfree
    • 如果程序不结束,并且用户不free,堆区空间不释放

三、分文件(多文件)编程

1.预处理:宏定义替换,头文件展开,不做语法检查

#define MAX 100 //后面没有分号   以后,出现`MAX`的地方都替换为100

//如果是系统的头文件,则用`<>`	#include <stdio.h>
//如果是用户的头文件,则用`""`	#include "a.h"
#include "a.h"	//把有文件的内容放在这句话的位置

c中,调用函数时,前面没有定义,别的地方有定义,不用声明,可以调用,有警告

c++中,调用函数时,前面没有定义,别的地方有定义,需要声明后,才可以调用

2.多文件头文件说明

  • 头文件中可放一些定义及声明
  • 在预处理时,头文件的内容会等效替换

四、随机数的产生

  1. 设置一个种子(种子一样,每次启动程序随机数都一样)

    srand(100);    //在<stdlib.h>头文件中有定义及声明(须引用)
    
  2. 生成随机数

    int i;
    int num;
    for(i = 0; i<10 ; ++i)
    {
      num = rand(); //在<stdlib.h>头文件中有定义及声明(须引用)
      printf("%d\n",num);
    }
    
  3. 利用系统时间函数产生随机数种子

    srand(time(NULL));//time()函数在<time.h>中有定义及声明(须引用)
    

五、windows接口

windows函数的测试

  • 读取字符

    #include <stdio.h>
    #include <conio.h>	//_getch();
    
    int main(void)
    {
    //	char ch = getchar();	//从键盘读取一个字符,需要按下回车
    	char ch =  _getch();	//从键盘读取一个字符,不需要按下回车
    	printf("ch = %d,ch = %c\n",ch,ch);
    	return 0;
    }
    
  • 检查键盘输入

    #include <stdio.h>
    #include <conio.h>	//_getch();	_kbhit();
    #include <Windows.h>//Sleep();//以毫秒为单位
    
    int main(void)
    {
    	while(1)
    	{
    		//int _kbhit(void); 功能:检测当前是否由键盘输入,若有返回一个非0值,否则返回0(假)
    		while(!_kbhit())
    		{
    			printf("没有键盘按下\n");
    			Sleep(1000);
    		}
    	}
    	return 0;
    }
    
  • 读取键值

    #include <stdio.h>
    #include <conio.h>	//_getch();
    
    int main(void)
    {
    	int i;
    	printf("请输入你要测试字符的次数:\n");
    	scanf("%d", &i);
    	for(i; i < 4; i++)
    	{
    		int a =  getch();	//从键盘读取一个字符,不需要按下回车
    		int b =  getch();	//上下左右按键需要读取两次
    		printf("键值为:%d %d\n",a, b);
    	}
    	return 0;
    }
    

六、墙的设计

  1. 所需要的公共宏定义

    • common.h

      #pragma once				//防止头文件包含重复
      
      #define CHAR_WALL	\'*\'		//墙
      #define CHAR_HEAD	\'@\'		//蛇头
      #define CHAR_BODY	\'#\'		//蛇身体
      #define CHAR_FOOD	\'%\'		//食物
      
      //方向键
      #define UP 		72
      #define DOWN 	80
      #define LEFT 	75
      #define RIGHT 	77
      
      //声明一个全局的二维数组
      #define ROW 15
      #define COL 15
      extern char game[ROW][COL];
      
      //移动在控制台屏幕上的坐标(在<windows.h>中有定义及声明)
      void gotoxy(int x, int y);
      
    • common.c

      #include <stdio.h>
      #include "common.h"
      #include <windows.h>
      
      //全局变量的定义
      char game[ROW][COL] = {0};
      
      //移动在控制台屏幕上的坐标	//<windows.h>中的函数,不用深究
      void gotoxy(int x, int y)
      {
      	COORD pos = {x,y};
      	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
      	SetConsoleCursorPosition(handle,pos);
      }
      
  2. 墙的初始化

    • 初始化以及打印墙

      void init_wall()	//初始化,设置墙壁
      {
      	int i,j;
      	memset(game,0,sizeof(game));	//清空
      	for(i=0;i<ROW;++i)
      	{
      		for(j=0;j<COL;++j)
      		{
      			if(i == 0||i == ROW-1||j == 0||j == COL-1)
      			{
      				game[i][j] = CHAR_WALL;	//墙壁赋值
      			}
      			else
      			{
      				game[i][j] = \' \';		//盒子的内部
      			}
      		}
      	}
      }
      
      void draw()	//打印数组的每个元素,绘图
      {
      	int i;
      	int j;
      	for(i=0;i<ROW;++i)
      	{
      		for(j=0;j<COL;++j)
      		{
      			printf("%c ",game[i][j]);
      		}
      		printf("\n");
      	}
      }
      
    • 获取数组内容

      void set(int i,int j,char ch)//改变数组某个元素(对应的某个下标)的内容
      {
      	game[i][j] = ch;
      }
      
      char get(int i,int j)		//改变数组某个元素(对应的某个下标)的内容
      {
      	return game[i][j];
      }
      
    • 蛇头碰墙判断

      int is_touch()	//判断设是否碰墙,如果碰墙返回1,没有返回0
      {
      	int i,j;
      	for(i=0;i<ROW;++i)
      	{
      		for(j=0;j<COL;++j)
      		{
      			if(i == 0||i == ROW-1||j == 0||j == COL-1)
      			{
      				if(game[i][j] == CHAR_HEAD)
      				{
      					return 1
      				}
      			}
      		}
      	}
      	return 0;
      }
      

七、食物的设计

  1. 随机坐标

    • 利用这两个头文件中的三个函数即可产生随机数
    #include <stdlib.h>	//srand(); srand();
    #include <time.h>	//time();
    
    //定义食物坐标,静态变量,只能在本文件使用
    static int food_x,food_y;
    
  2. 食物不能在墙壁上

    1. 首先进行求余算法

      任意数 % 10 所得结果范围为0~9

      任意数 % 5 所得结果范围为0~4

    2. 同理可知,要想食物不在墙上(墙有四面)

      产生的随机数 % ROW(COL-2) 所得结果为0~墙的下标减2

    3. 因此最终限制的范围仍要加1

      food_x = rand()%(ROW(COL) - 2) + 1;

  3. 食物不能在蛇所在位置

    • 这里用if判断即可

      if(game[food_x][food_y] != CHAR_HEAD||game[food_x][food_y] != CHAR_BODY)
      {
      game[food_x][food_y] = CHAR_FOOD;
      break;
      }
      
  4. 获取食物的下标

    • 两个返回函数

      int get_food_x()		//获取食物的x坐标
      {
      	return food_x;
      }
      int get_food_y()		//获取食物的y坐标
      {
      	return food_y;
      }
      

八、蛇的设计

  1. 蛇的移动原理(链表)

    • 新增一个节点,设置为蛇头
    • 将原来的头节点(蛇头)的内容设置为蛇的身体
    • 把尾节点对应的内容设置为空,并把尾节点删除
  2. 删除和新增节点

    //static变量,只能本文件使用
    static void add_point(int x, int y)
    {
    	Point *pnew = (Point *)malloc(sizeof(Point));
    	pnew->x = x;
    	pnew->y = y;
    	//一个节点都没有时,新节点就是头节点,也是尾节点
    	if(head == NULL)
    	{
    		head = pnew;
    		pnew->next = NULL;//尾节点的标志
    	}
    	else
    	{
          //把原来的头节点的内容设置成蛇的身体
          //新节点当作头节点
          //原来就已经存在节点
    		//1.把原来的头节点的内容设置成蛇的身体
    		set(head->x,head->y,CHAR_BODY);
    		//2.新节点当作头节点
    		pnew->next = head;
    		head = pnew;
    	}
    	//设置头节点的内容
    	set(head->x,head->y,CHAR_HEAD);
    }
    
    //删除尾节点,static函数,只能在本文件使用
    static void del_point()
    {
    	//pre和tmp相差两个节点,tmp靠后
    	Point *pre = head;
    	Point *tmp = head->next;
    
    	//必须有两个以上的节点才能做删除操作
    	if(head == NULL||head->next == NULL)
    	{
    		return;
    	}
    	while(tmp->next != NULL)
    	{
    		//节点各自往后移动
    		pre = pre->next;
    		tmp = tmp->next;
    	}
    	//tmp就是尾节点,pre就是尾节点的上一个节点
    	//把尾节点对应的内容设置为空,把尾节点删除
    	set(tmp->x,tmp->y,\' \');
    	free(tmp);
    	tmp = NULL;
    	pre->next = NULL;
    }
    
  3. 蛇的移动

    • 向上移动:--x
    • 向下移动:++x
    • 向左移动:--y
    • 向右移动:`++y
    //如果成功移动:只是移动(没有碰到墙壁、身体),吃到食物,返回1
    //如果成功失败:碰到墙壁、身体,返回0
    int snake_move(char key)
    {
    	int x = head->x;
    	int y = head->y;
    
    	//方向的移动
    	switch(key)
    	{
    	case UP:
    		--x;
    		break;
    	case DOWN:	
    		++x;
    		break;
    	case LEFT:
    		--y;
    		break;
    	case RIGHT:
    		++y;
    		break;
    	}
    
    	//蛇是否碰到墙壁或身体,返回0
    	if(1 == is_touch()||game[x][y] == CHAR_BODY)
    	{
    		return 0;
    	}
    
    	//如果成功移动:只是移动(没有碰到墙壁、身体),吃到食物,返回1
    	if(x == get_food_x() && y == get_food_y())
    	{
    		add_point(x,y);
    		set_food();
    	}
    	else
    	{
    		add_point(x,y);
    		del_point();
    	}
    	return 1;
    }
    

九、整个游戏的流程控制

  • 关于system函数的用法

    • 清屏

      system("cls");

    • 设置颜色

      system("color +颜色参数");

      颜色参数:

      0 = 黑色 8 = 灰色
      1 = 蓝色 9 = 淡蓝色
      2 = 绿色 A = 淡绿色
      3 = 湖蓝色 B = 淡浅绿色
      4 = 红色 C = 淡红色
      5 = 紫色 D = 淡紫色
      6 = 黄色 E = 淡黄色
      7 = 白色 F = 亮白色

    • 延时

      system("pause");

  • 防光标刷屏函数

    //移动在控制台屏幕上的坐标	//<windows.h>中的函数,不用深究
    void gotoxy(int x, int y)
    {
    	COORD pos = {x,y};
    	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    	SetConsoleCursorPosition(handle,pos);
    }
    
  • 详细流程控制参考main.c

源代码:

main.c

#include <stdio.h>
#include <stdlib.h>	//system();
#include <conio.h>	//_getch();
#include <Windows.h>//Sleep();
#include "wall.h"	//memset(); food(); draw();
#include "snake.h"	//Point init_snake();
#include "common.h"	//定义的名字

int main()
{
	int is_start = 1;	//1代表已经开始,0代表结束
	int is_dead =0;		//0代表蛇还活着,1代表蛇已经死了

	while(1 == is_start)
	{
		is_dead = 0;	//代表蛇还活着

		init_wall();	//初始化,设置墙壁
		init_snake();	//蛇
		set_food();		//设置食物

		system("cls");
		system("color 3E");//设置颜色
		draw();			//打印数组的每个元素,绘图
		readme();		//打印游戏说明
		while(0 == is_dead)
		{
			char key = _getch();//读取键盘的方向键

			//如果键盘没有按下,蛇沿着原来的方向移动
			while(0 == _kbhit())
			{
				if(UP==key || DOWN==key || LEFT==key || RIGHT==key)
				{
					//设置打印的位置,从(0,0)开始
					gotoxy(0, 0);//也可以用system("cls");但是会闪屏
					if(1 == snake_move(key))
					{
						draw();//重新打印数组内容
						Sleep(300);//在头文件<Windows.h>中
					}
					else
					{
						is_dead = 1;//蛇已经死了
						break;//while(0 == _kbhit())
					}
				}
			}
		}
	}
}

common.c

#include <stdio.h>
#include "common.h"
#include <windows.h>

//全局变量的定义
char game[ROW][COL] = {0};

//移动在控制台屏幕上的坐标	//<windows.h>中的函数,不用深究
void gotoxy(int x, int y)
{
	COORD pos = {x,y};
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(handle,pos);
}

snake.c

#include <stdio.h>	//NULL
#include <stdlib.h>	//malloc();
#include "snake.h"	//Point
#include "wall.h"	//set();
#include "common.h"	//初始化定义

//static变量,只能本文件使用
static Point *head = NULL;

//static函数,只能本文件使用
static void add_point(int x, int y)
{
	Point *pnew = (Point *)malloc(sizeof(Point));
	pnew->x = x;
	pnew->y = y;

	//一个节点都没有时,新节点就是头节点,也是尾节点
	if(head == NULL)
	{
		head = pnew;
		pnew->next = NULL;//尾节点的标志
	}
	else
	{
		//原来就已经存在节点
		//新节点当作头节点
		//把原来的头节点的内容设置成蛇的身体

		//1.把原来的头节点的内容设置成蛇的身体
		set(head->x,head->y,CHAR_BODY);
		//2.新节点当作头节点
		pnew->next = head;
		head = pnew;
	}
	//设置头节点的内容
	set(head->x,head->y,CHAR_HEAD);
}

//删除尾节点,static函数,只能在本文件使用
static void del_point()
{
	//pre和tmp相差两个节点,tmp靠后
	Point *pre = head;
	Point *tmp = head->next;

	//必须有两个以上的节点才能做删除操作
	if(head == NULL||head->next == NULL)
	{
		return;
	}
	while(tmp->next != NULL)
	{
		//节点各自往后移动
		pre = pre->next;
		tmp = tmp->next;
	}
	//tmp就是尾节点,pre就是尾节点的上一个节点
	//把尾节点对应的内容设置为空,把尾节点删除
	set(tmp->x,tmp->y,\' \');
	free(tmp);
	tmp = NULL;
	pre->next = NULL;
}


void free_snake()	//释放节点
{
	Point *del = head;
	while(del != NULL)
	{
		head = head->next;	//释放前保存下一个节点
		free(del);			//释放节点
		del = head;			//重新设置需要释放的节点
	}
}


//如果成功移动:只是移动(没有碰到墙壁、身体),吃到食物,返回1
//如果成功失败:碰到墙壁、身体,返回0
int snake_move(char key)
{
	int x = head->x;
	int y = head->y;

	//方向的移动
	switch(key)
	{
	case UP:
		--x;
		break;
	case DOWN:	
		++x;
		break;
	case LEFT:
		--y;
		break;
	case RIGHT:
		++y;
		break;
	}

	//蛇是否碰到墙壁或身体,返回0
	if(1 == is_touch()||game[x][y] == CHAR_BODY)
	{
		return 0;
	}

	//如果成功移动:只是移动(没有碰到墙壁、身体),吃到食物,返回1
	if(x == get_food_x() && y == get_food_y())
	{
		add_point(x,y);
		set_food();
	}
	else
	{
		add_point(x,y);
		del_point();
	}
	return 1;
}

void init_snake()	//初始化蛇
{
	free_snake();	//
	add_point(4,2);
	add_point(4,3);
	add_point(4,4);
}

wall.c

#include <stdio.h>	//printf();
#include <string.h>	//memset();
#include <stdlib.h>	//srand(); srand();
#include <time.h>	//time();
#include "common.h"	//char game[ROW][COL];

//定义食物坐标,静态变量,只能在本文件使用
static int food_x,food_y;

void init_wall()	//初始化,设置墙壁
{
	int i,j;
	memset(game,0,sizeof(game));	//清空
	for(i=0;i<ROW;++i)
	{
		for(j=0;j<COL;++j)
		{
			if(i == 0||i == ROW-1||j == 0||j == COL-1)
			{
				game[i][j] = CHAR_WALL;	//墙壁赋值
			}
			else
			{
				game[i][j] = \' \';		//盒子的内部
			}
		}
	}
}


void draw()	//打印数组的每个元素,绘图
{
	int i;
	int j;
	for(i=0;i<ROW;++i)
	{
		for(j=0;j<COL;++j)
		{
			printf("%c ",game[i][j]);
		}
		printf("\n");
	}
	gotoxy(0, 0);//防止闪屏
}


void set(int x,int y,char ch)//改变数组某个元素(对应的某个下标)的内容
{
	game[x][y] = ch;
}


char get(int i,int j)//改变数组某个元素(对应的某个下标)的内容
{
	return game[i][j];
}

int is_touch()//判断设是否碰墙,如果碰墙返回1,没有返回0
{
	int i,j;
	for(i=0;i<ROW;++i)
	{
		for(j=0;j<COL;++j)
		{
			if(i == 0||i == ROW-1||j == 0||j == COL-1)
			{
				if(game[i][j] == CHAR_HEAD)
				{
					return 1;
				}
			}
		}
	}
	return 0;
}


/*********************************************
1.随机坐标
2.食物不能在墙壁上
3.食物不能在蛇所在位置
*********************************************/
void set_food()			//设置食物
{
	while(1)
	{
		//设置种子
		srand((unsigned int)time(NULL));

		//做了限定处理,食物不能在墙壁上
		food_x = rand()%(ROW - 2) + 1;	//X
		food_y = rand()%(COL - 2) + 1;	//Y

		if(game[food_x][food_y] != CHAR_HEAD||game[food_x][food_y] != CHAR_BODY)
		{
			game[food_x][food_y] = CHAR_FOOD;
			break;
		}
	}
}


int get_food_x()		//获取食物的x坐标
{
	return food_x;
}


int get_food_y()		//获取食物的y坐标
{
	return food_y;
}

void readme()
{
	gotoxy(80,1);
	printf("贪吃蛇游戏");
	gotoxy(75,2);
	printf("1.按任意键暂停");
	gotoxy(75,3);
	printf("2.移动上、下、左、右操纵蛇移动");
	gotoxy(75,4);
	printf("2.碰到墙壁或自己,游戏结束");
}

common.h

#pragma once				//防止头文件包含重复

#define CHAR_WALL	\'*\'		//墙
#define CHAR_HEAD	\'@\'		//蛇头
#define CHAR_BODY	\'&\'		//蛇身体
#define CHAR_FOOD	\'%\'		//食物

//方向键
#define UP 		72
#define DOWN 	80
#define LEFT 	75
#define RIGHT 	77

//声明一个全局的二维数组
#define ROW 30
#define COL 30
extern char game[ROW][COL];

//移动在控制台屏幕上的坐标
void gotoxy(int x, int y);

snake.h

#pragma once		//防止头文件包含重复

typedef struct Point
{
	//数据域(位置)
	int x, y;
	//指针域
	struct Point *next;
}Point;


extern void init_snake();	//初始化蛇
extern void free_snake();	//释放节点

//如果成功移动:只是移动(没有碰到墙壁、身体),吃到食物,返回1
//如果成功失败:碰到墙壁、身体,返回0
extern int snake_move(char key);

wall.h

#pragma once			//防止头文件包含重复

extern void init_wall();		//初始化,设置墙壁

extern void draw();				//打印数组的每个元素,绘图

extern void set(int i,int j,char ch);//改变数组某个元素(对应的某个下标)的内容

extern char get(int i,int j);	//获取数组相应的内容

extern int is_touch();			//判断设是否碰墙,如果碰墙返回1,没有返回0

extern void set_food();				//设置食物

extern int get_food_x();		//获取食物的x坐标

extern int get_food_y();		//获取食物的x坐标

extern void readme();			//游戏说明

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C# 线程与任务发布时间:2022-07-13
下一篇:
[C++]C风格、C++风格和C++11特性的线程池发布时间:2022-07-13
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap