贪吃蛇的实现
一、作用域和生命周期
-
以开房为例子举例(变量)
开房:给了房费(
malloc
函数或者定义变量),分配一个房间(空间)作用域:在某个特定的房间(空间),起作用的范围
- 局部变量的作用域:在某个
{}
里面
生命周期:退房了(
free
或者系统自行释放),不能再使用这个空间- 局部变量离开了
{}
,无法在使用
- 局部变量的作用域:在某个
-
各种变量
普通局部变量:(
int a;
)- 通常放在某个函数、某个语句、某个
{}
里面 - 程序执行到定义语句时,才会给a分配空间
- 离开
{}
,a自动回收、释放。所谓释放,不是空间消失,而是这个空间用户不能再用 - 局部变量,不初始化,值为垃圾数据,随机数,不同编译器的至不一样
1)系统维护内存,自动分配空间,自动释放,自动变量
static
局部变量:(static int a;
)- 在
main
调用之前,就已经分配空间,就已经初始化 - 整个程序结束后,
static
局部变量才会释放 static
局部变量只能用常量初始化- 还叫局部变量,站在作用域角度看,该变量只能在
{}
内部直接使用
1)在
main
函数调用前,已经分配空间,已经初始化,不人为初始化,自动赋值为02)整个程序结束后,
static
局部变量才会释放
普通全局变量:(
int a;
)1)真正的全局,所有地方都能使用的变量
-
定义在
函数外部
的变量就是全局变量 -
全局变量,任何地方都能使用这个变量
-
使用这个全局变量时,如果定义不在前面,需要声明
-
所谓的声明,告诉编译器,这个变量有定义的,只是放在别的地方定义
extern int a;//声明不能赋值 `extern`:1.用于全局变量声明 2.用于全局函数声明
-
可以在定义时同时初始化,在函数外部,不能单独给全局变量赋值
2)在
main
函数调用前,已经分配空间,已经初始化,不人为初始化,自动赋值为03)整个程序结束后,普通变量才会释放
4)只能定义一次,可以声明多次
-
声明和定义(包括多文件)
- 全局变量或全局函数,可以声明多次,但是只能定义一次
extern
只是声明,没有定义,没有定义就无法赋值(声明不能赋值,不建立存储空间)- 建议:定义全局变量要赋值,声明时,加
extern
int a; //定义 extern int a;//声明
static
全局变量:(static int a;
)1)针对某个文件的全局变量
- 在
main
函数调用之前,就已经分配空间,整个程序结束,才释放 static
全局变量,在每个文件可以定义一次,没有声明一说
2)在
main
函数调用前,已经分配空间,已经初始化,不人为初始化,自动赋值为03)定义在函数外部
- 通常放在某个函数、某个语句、某个
二、内存分区
程序:通过gcc
编译器,翻译成可执行程序(机器能识别的二进制代码),这个可执行程序,可以提前规划内存布局,没有加载内存:
-
text
(代码区):程序入口地址,函数 -
date
:初始化全局变量,static
变量,可读可写 只读区,文字常量区,放字符串常量,例:"aaa"
-
bss
:没有初始化的全局变量或static
变量(用户没有指定初始化的值),在main
函数前,由系统初始化为0
把可执行程序运行起来,它就成为进程,他需要内存,由系统加载:(运行时才有)
stack
(栈区):- 普通局部变量
heap
(堆区):- 通过用户申请和释放空间,
malloc
、free
- 如果程序不结束,并且用户不
free
,堆区空间不释放
- 通过用户申请和释放空间,
三、分文件(多文件)编程
1.预处理:宏定义替换,头文件展开,不做语法检查
#define MAX 100 //后面没有分号 以后,出现`MAX`的地方都替换为100 //如果是系统的头文件,则用`<>` #include <stdio.h> //如果是用户的头文件,则用`""` #include "a.h" #include "a.h" //把有文件的内容放在这句话的位置
在
c
中,调用函数时,前面没有定义,别的地方有定义,不用声明,可以调用,有警告在
c++
中,调用函数时,前面没有定义,别的地方有定义,需要声明后,才可以调用2.多文件头文件说明
- 头文件中可放一些定义及声明
- 在预处理时,头文件的内容会等效替换
四、随机数的产生
-
设置一个种子(种子一样,每次启动程序随机数都一样)
srand(100); //在<stdlib.h>头文件中有定义及声明(须引用)
-
生成随机数
int i; int num; for(i = 0; i<10 ; ++i) { num = rand(); //在<stdlib.h>头文件中有定义及声明(须引用) printf("%d\n",num); }
-
利用系统时间函数产生随机数种子
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; }
六、墙的设计
-
所需要的公共宏定义
-
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); }
-
-
墙的初始化
-
初始化以及打印墙
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; }
-
七、食物的设计
-
随机坐标
- 利用这两个头文件中的三个函数即可产生随机数
#include <stdlib.h> //srand(); srand(); #include <time.h> //time(); //定义食物坐标,静态变量,只能在本文件使用 static int food_x,food_y;
-
食物不能在墙壁上
-
首先进行求余算法
任意数 % 10
所得结果范围为0~9
任意数 % 5
所得结果范围为0~4
-
同理可知,要想食物不在墙上(墙有四面)
产生的随机数 % ROW(COL-2)
所得结果为0~墙的下标减2
-
因此最终限制的范围仍要加
1
food_x = rand()%(ROW(COL) - 2) + 1;
-
-
食物不能在蛇所在位置
-
这里用
if
判断即可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; }
-
八、蛇的设计
-
蛇的移动原理(链表)
- 新增一个节点,设置为蛇头
- 将原来的头节点(蛇头)的内容设置为蛇的身体
- 把尾节点对应的内容设置为空,并把尾节点删除
-
删除和新增节点
//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; }
-
蛇的移动
- 向上移动:
--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(); //游戏说明
请发表评论