赞
踩
朋友们,经过我的c语言前十章的知识,大家应该已经入门了,我个人认为学习c语言最好的方法就是造轮子,写项目就是最好的提高方法,可以把我们学过的知识串联起来。
- #include<stdio.h>
- #include<time.h>//随机种子来出现食物
- #include<conio.h>//监听键盘输入
- #include<windows.h>//为了使用gotoxy(光标移动函数)
-
- //■(创建地图、食物) ⊙(蛇头) ●(蛇身)
-
- #define MAP_MODE "■"//地图模块
- #define SNAKE_HEAD "⊙"//蛇头
- #define SNAKE_BODY "●"//蛇身
- #define MAP_WIDTH 80//地图长度
- #define MAP_HEIGHT 30//地图宽度
- #define MOVE_CENTER 12//地图挪位置
- #define INITLEN 3//定义蛇的初始长度
- #define MAXLEN 30//定义蛇的最大长度
-
- void createMap();
- void createFood();
- void initSnake();
- void moveSnake();
- int statement();
- void gotoxy(int x, int y);
-
- struct Food {//食物结构体
- int x;
- int y;
- }food;
-
- struct Snake {//蛇的结构体
- int x[MAXLEN];
- int y[MAXLEN];
- int currentLen;//当前蛇的长度 x[0],y[0] ->蛇头
- }snake;
-
-
- int direct = 'a';
-
- int flag = 1;//是否需要生成食物
-
- int main() {
- createMap();
- while (1) {
- Sleep(300);
- if (flag) {
- createFood();
- }
- moveSnake();
- if (statement()) {
- gotoxy(MAP_WIDTH / 2, MAP_HEIGHT / 2);
- printf("Game Over!\n");
- //改变光标位置,使其不影响美观
- gotoxy(96, 0);
- exit(0);
- }
- }
-
- return 0;
- }
- void createMap() {
- for (int i = 0 + MOVE_CENTER; i < MAP_WIDTH + MOVE_CENTER; i += 2) {//上下边
- gotoxy(i, 0);//改变x,y不变->最上面一条边
- printf(MAP_MODE);
-
- gotoxy(i, MAP_HEIGHT - 1);//改变x,y不变->最下面一条边
- printf(MAP_MODE);
- }
-
- for (int i = 0; i < MAP_HEIGHT; i++) {//左右边
- gotoxy(0 + MOVE_CENTER, i);//改变y,x不变->最左面一条边
- printf(MAP_MODE);
-
- gotoxy(MAP_WIDTH + MOVE_CENTER, i);//改变y,x不变->最右面一条边
- printf(MAP_MODE);
- }
- //改变光标位置,使其不影响美观
- gotoxy(96, 0);
-
- //初始化蛇
- initSnake();
- }
- void createFood() {
- //随机出现食物
- srand(time(NULL));//随机种子
- int isCreate = 1;//表示食物是否可以被创建
- food.x = rand() % (MAP_WIDTH - 4 + 2) + MOVE_CENTER;
- food.y = rand() % (MAP_HEIGHT - 1 + 1);
-
- if (food.x % 2 == 0) {//满足x坐标为偶数
- //食物的坐标不能在蛇的身上
- for (int i = 0; i < snake.currentLen; i++) {
- if (snake.x[i] == food.x && snake.y[i] == food.y) {
- isCreate = 0;
- }
-
- if (isCreate) {
- gotoxy(food.x, food.y);
- printf(MAP_MODE);
- flag = 0;
- //改变光标位置,使其不影响美观
- gotoxy(96, 0);
- }
- }
- }
- }
- void initSnake() {
- snake.currentLen = INITLEN;
- snake.x[0] = MAP_WIDTH / 2 + MOVE_CENTER;
- snake.y[0] = MAP_HEIGHT / 2;
-
- gotoxy(snake.x[0], snake.y[0]);
- printf(SNAKE_HEAD);
-
- //用循环打印出蛇身,蛇身接到蛇头的后面
- for (int i = 1; i < snake.currentLen; i++) {
- snake.x[i] = snake.x[i - 1] + 2;
- snake.y[i] = snake.y[i - 1];
-
- gotoxy(snake.x[i], snake.y[i]);
- printf(SNAKE_BODY);
- }
-
- //改变光标位置,使其不影响美观
- gotoxy(96, 0);
-
-
- }
- void moveSnake() {
-
- if (_kbhit()) {//监听键盘输入
- fflush(stdin);
- direct = _getch();
- }
-
- //擦除最后一节蛇尾
- gotoxy(snake.x[snake.currentLen - 1], snake.y[snake.currentLen - 1]);
- printf(" ");//打印两个光标大小的空格
-
- //开始替换坐标,移动蛇
- for (int i = snake.currentLen - 1; i > 0; i--) {
- snake.x[i] = snake.x[i - 1];
- snake.y[i] = snake.y[i - 1];
- gotoxy(snake.x[i], snake.y[i]);
- printf(SNAKE_BODY);
- }
-
- switch (direct) {
- case 'w':
- case 'W':
- snake.y[0]--;
- break;
-
- case 's':
- case 'S':
- snake.y[0]++;
- break;
-
- case 'a':
- case 'A':
- snake.x[0] -= 2;
- break;
-
- case 'd':
- case 'D':
- snake.x[0] += 2;
- break;
- }
-
- //移动之后,新蛇头的位置
- gotoxy(snake.x[0], snake.y[0]);
- printf(SNAKE_HEAD);
-
- //改变光标位置,使其不影响美观
- gotoxy(96, 0);
-
- //判断蛇头的坐标是否和食物相等
- if (snake.x[0] == food.x && snake.y[0] == food.y) {
- snake.currentLen++;
- flag = 1;
- }
-
- }
- int statement() {//判断游戏当前的状态
- //判断蛇头是否撞到墙壁
- if (snake.x[0] == 0 + MOVE_CENTER || snake.x[0] == MAP_WIDTH + MOVE_CENTER || snake.y[0] == 0 || snake.y[0] == MAP_HEIGHT - 1) {
- return 1;
- }
- //蛇头吃到自己身体的任意部位
-
- for (int i = 1; i < snake.currentLen; i++) {
- if (snake.x[0] == snake.x[i] && snake.y[0] == snake.y[i]) {
- return 1;
- }
- }
- return 0;
- }
- void gotoxy(int x, int y) {//系统辅助函数
- COORD pos = { x, y };
- HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
- SetConsoleCursorPosition(hOut, pos);
- }

今天我们写一个实战项目贪吃蛇:核心会用到数组和gotoxy();函数
我们先看看成品:
这是个看起来有点简陋的贪吃蛇程序,虽然简单但是写的时候发现问题解决问题是最宝贵的财富。
我们首先来看这个程序有什么:
1.地图
2.蛇
3.食物
根据这三个我们来对贪吃蛇程序分模块:
1.地图模块
2.随机生成食物模块
3.初始化蛇身模块
4.贪吃蛇移动模块
5.游戏状态模块
6.坐标控制模块
分析完毕,我们开始进入正题:
代码如下
- void createMap() {
- for (int i = 0 + MOVE_CENTER; i < MAP_WIDTH + MOVE_CENTER; i += 2) {//上下边
- gotoxy(i, 0);//改变x,y不变->最上面一条边
- printf(MAP_MODE);
-
- gotoxy(i, MAP_HEIGHT - 1);//改变x,y不变->最下面一条边
- printf(MAP_MODE);
- }
-
- for (int i = 0; i < MAP_HEIGHT; i++) {//左右边
- gotoxy(0 + MOVE_CENTER, i);//改变y,x不变->最左面一条边
- printf(MAP_MODE);
-
- gotoxy(MAP_WIDTH + MOVE_CENTER, i);//改变y,x不变->最右面一条边
- printf(MAP_MODE);
- }
- //改变光标位置,使其不影响美观
- gotoxy(96, 0);
-
- //初始化蛇
- initSnake();
- }
-

首先我们写地图就了解了第一个知识点:gotoxy(int x, int y);函数:
gotoxy(int x, int y)是 #include<windows.h> 中声明的一个函数,功能是将光标移动到指定位置。在当代的 Visual C++ 或 GCC 中可以自定义这个函数。
我画了张图,便于大家理解:
左上角为原点(0,0),x往右递增,y往下递增。
怎么画的这个方框地图呢?:
首先我们画上下的框:
for (int i = 0 + MOVE_CENTER; i < MAP_WIDTH + MOVE_CENTER; i += 2) {//上下边
gotoxy(i, 0);//改变x,y不变->最上面一条边
printf(MAP_MODE);
gotoxy(i, MAP_HEIGHT - 1);//改变x,y不变->最下面一条边
printf(MAP_MODE);
}
利用for循环循环输出我们的MAP_MODE方框,然后给坐标函数赋值循环打印
你可能有点疑惑,为什么是i+=2而不是i++,因为■,这个东西,占两个光标宽度;
这里我们宏定义一下:
#define MAP_MODE "■"//地图模块
#define MAP_WIDTH 80//地图长度
#define MAP_HEIGHT 30//地图宽度
#define MOVE_CENTER 12//地图挪位置
为啥用宏定义?为了方便我们下次修改,不用单独取修改模块,直接修改宏定义。
我们设地图长度为80,宽度为30;
挪位置是把地图往右边挪一挪,可以更美观
注意:■,这个东西,占两个光标宽度;
好的,同理我们开始画出左右边:
for (int i = 0; i < MAP_HEIGHT; i++) {//左右边
gotoxy(0 + MOVE_CENTER, i);//改变y,x不变->最左面一条边
printf(MAP_MODE);
gotoxy(MAP_WIDTH + MOVE_CENTER, i);//改变y,x不变->最右面一条边
printf(MAP_MODE);
}
最后我们把光标移到地图外面去,免得影响阅读:
//改变光标位置,使其不影响美观
gotoxy(96, 0);
之后初始化蛇,我们先写着,等会儿我来讲初始化蛇模块:
//初始化蛇
initSnake();
}
因为地图模块我们用到了这个gotoxy();函数,他隶属于坐标模块,故我们提到前面来讲这个模块:
- void gotoxy(int x, int y) {//系统辅助函数
- COORD pos = { x, y };
- HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
- SetConsoleCursorPosition(hOut, pos);
- }
gotoxy(),可以根据你赋值的坐标,把光标移到相应处.
你只要记住,要用gotoxy();函数你得这么定义,并且带上头文件#include<windows.h>//为了使用gotoxy(光标移动函数)
下次用的坐标函数,直接套用;
代码如下:
- void createFood() {
- //随机出现食物
- srand(time(NULL));//随机种子
- int isCreate = 1;//表示食物是否可以被创建
- food.x = rand() % (MAP_WIDTH - 4 + 2) + MOVE_CENTER;
- food.y = rand() % (MAP_HEIGHT - 1 + 1);
-
- if (food.x % 2 == 0) {//满足x坐标为偶数
- //食物的坐标不能在蛇的身上
- for (int i = 0; i < snake.currentLen; i++) {
- if (snake.x[i] == food.x && snake.y[i] == food.y) {
- isCreate = 0;
- }
-
- if (isCreate) {
- gotoxy(food.x, food.y);
- printf(MAP_MODE);
- flag = 0;
- //改变光标位置,使其不影响美观
- gotoxy(96, 0);
- }
- }
- }
- }

我们先来分析食物,食物也是个方框,他有x与y坐标。
分析完毕,我们来创建结构体:
struct Food {//食物结构体
int x;
int y;
}food;
好了,我们有了食物,现在我们要随机生成在我们的地图里,就是说他的活动范围:
怎么随机呢?
srand(time(NULL));//随机种子
同时time()函数要带上头文件#include<time.h>//随机种子来出现食物,因为是利用系统时间.
这条命令的作用是利用系统时间,返回一个随机数。
同时,我们要让食物生成在我们想要的位置:
不能再地图外,不能在蛇的身体上生成。
我们干脆定义一个int isCreate = 1;//表示食物是否可以被创建
下面的代码表示:食物的x坐标与y坐标可以生成的位置:
food.x = rand() % (MAP_WIDTH - 4 + 2) + MOVE_CENTER;
food.y = rand() % (MAP_HEIGHT - 1 + 1);
如果你不了解rand函数,请你点击下面链接了解,我们不多赘述:
https://blog.csdn.net/cmm0401/article/details/54599083
了解过后你可能要问,为啥是食物x坐标是在4到地图宽度之间,因为■,这个东西,占两个光标宽度,乘个2;
再写个for循环,使食物不生成在蛇身上:
if (food.x % 2 == 0) {//满足x坐标为偶数因为■,这个东西,占两个光标宽度,乘个2;
//食物的坐标不能在蛇的身上
for (int i = 0; i < snake.currentLen; i++) {
if (snake.x[i] == food.x && snake.y[i] == food.y) {
isCreate = 0;
}
snake结构体我们之后就讲,snake.currentLen是表示蛇的长度:
然后我们写可以生成食物的if循环;
if (isCreate) {
gotoxy(food.x, food.y);
printf(MAP_MODE);
flag = 0;
//改变光标位置,使其不影响美观
gotoxy(96, 0);
}
flag是我定义的全局变量:int flag = 1;//是否需要生成食物
代码如下:
- void initSnake() {
- snake.currentLen = INITLEN;
- snake.x[0] = MAP_WIDTH / 2 + MOVE_CENTER;
- snake.y[0] = MAP_HEIGHT / 2;
-
- gotoxy(snake.x[0], snake.y[0]);
- printf(SNAKE_HEAD);
-
- //用循环打印出蛇身,蛇身接到蛇头的后面
- for (int i = 1; i < snake.currentLen; i++) {
- snake.x[i] = snake.x[i - 1] + 2;
- snake.y[i] = snake.y[i - 1];
-
- gotoxy(snake.x[i], snake.y[i]);
- printf(SNAKE_BODY);
- }
-

首先我们写蛇这个结构体我们得知道他由哪些组成:
x坐标,y坐标,还有蛇的长度,蛇肯定不是单一坐标组成,我们使用数组表示。
根据这个我们开始定义蛇结构体:
struct Snake {//蛇的结构体
int x[MAXLEN];
int y[MAXLEN];
int currentLen;//当前蛇的长度 x[0],y[0] ->蛇头
}snake;
定义宏变量
#define INITLEN 3//定义蛇的初始长度
#define MAXLEN 30//定义蛇的最大长度
#define SNAKE_HEAD "⊙"//蛇头
#define SNAKE_BODY "●"//蛇身
毫无疑问,蛇头就是数组的第一个元素x[0]y[0];
snake.currentLen = INITLEN; //蛇长度定义为初始长度
snake.x[0] = MAP_WIDTH / 2 + MOVE_CENTER; //蛇头x坐标出现在地图中央
snake.y[0] = MAP_HEIGHT / 2; //y坐标也应该在中央
gotoxy(snake.x[0], snake.y[0]); //光标移到地图中央
printf(SNAKE_HEAD); //输出蛇头
蛇头写完了我们来写蛇身:
//用循环打印出蛇身,蛇身接到蛇头的后面
for (int i = 1; i < snake.currentLen; i++) {
snake.x[i] = snake.x[i - 1] + 2; //加2因为方框占两格
snake.y[i] = snake.y[i - 1];
gotoxy(snake.x[i], snake.y[i]);
printf(SNAKE_BODY);
}
//改变光标位置,使其不影响美观
gotoxy(96, 0);
代码如下:
- void moveSnake() {
-
- if (_kbhit()) {//监听键盘输入
- fflush(stdin);
- direct = _getch();
- }
-
- //擦除最后一节蛇尾
- gotoxy(snake.x[snake.currentLen - 1], snake.y[snake.currentLen - 1]);
- printf(" ");//打印两个光标大小的空格
-
- //开始替换坐标,移动蛇
- for (int i = snake.currentLen - 1; i > 0; i--) {
- snake.x[i] = snake.x[i - 1];
- snake.y[i] = snake.y[i - 1];
- gotoxy(snake.x[i], snake.y[i]);
- printf(SNAKE_BODY);
- }
-
- switch (direct) {
- case 'w':
- case 'W':
- snake.y[0]--;
- break;
-
- case 's':
- case 'S':
- snake.y[0]++;
- break;
-
- case 'a':
- case 'A':
- snake.x[0] -= 2;
- break;
-
- case 'd':
- case 'D':
- snake.x[0] += 2;
- break;
- }
-
- //移动之后,新蛇头的位置
- gotoxy(snake.x[0], snake.y[0]);
- printf(SNAKE_HEAD);
-
- //改变光标位置,使其不影响美观
- gotoxy(96, 0);
-
- //判断蛇头的坐标是否和食物相等
- if (snake.x[0] == food.x && snake.y[0] == food.y) {
- snake.currentLen++;
- flag = 1;
- }
-
- }

首先,又遇到我们不熟悉的函数kbhit()函数,请看以下链接讲解,我们不多赘述:
看完之后你就知道他是监听键盘输入的,因为我们要用键盘控制蛇的移动。
用的时候别忘了带上头文件#include<conio.h>//监听键盘输入
我们了解下,贪吃蛇怎么移动的,吃了一个食物,贪吃蛇长一格,,蛇移动所有方块都在动么,不是蛇头和设为动,而蛇尾动就是擦除最后一格,是不是想起来我们小时候玩的老版勇者斗恶龙的像素游戏,也是利用这种一帧帧的改变做的,然后视觉上你觉得勇者在运动。
回归正题:
怎么擦除我们的蛇尾呢:
//擦除最后一节蛇尾
gotoxy(snake.x[snake.currentLen - 1], snake.y[snake.currentLen - 1]);
printf(" ");//打印两个光标大小的空格
把蛇的x坐标改为长度-1,y坐标也-1.然后把方框变成“ ”,覆盖掉。
然后我们开始移动蛇身了:
/开始替换坐标,移动蛇
for (int i = snake.currentLen - 1; i > 0; i--) {
snake.x[i] = snake.x[i - 1];
snake.y[i] = snake.y[i - 1];
gotoxy(snake.x[i], snake.y[i]);
printf(SNAKE_BODY);
}
写一个for循环一直自检把x-1的坐标赋给x,前一个坐标给后一个,这不就是在运动么,然后在座标处,我们输出蛇身。
蛇头是控制蛇身的,怎么控制蛇头呢,这就到了我们之前讲过的kbhit()函数了,既然监听了键盘输入,那我们就输入控制蛇头坐标的按键。
宏定义:int direct = 'a';//默认按键为a
为啥要这个宏定义,因为这默认我们的蛇往左运动。
switch (direct) {
case 'w':
case 'W':
snake.y[0]--; //向上
break;
case 's':
case 'S':
snake.y[0]++; //向下
break;
case 'a':
case 'A':
snake.x[0] -= 2; //向左
break;
case 'd':
case 'D':
snake.x[0] += 2; //向右
break;
}
//移动之后,新蛇头的位置
gotoxy(snake.x[0], snake.y[0]);
printf(SNAKE_HEAD);
//改变光标位置,使其不影响美观
gotoxy(96, 0);
用switch语句控制按键操纵蛇头坐标,这样我们就可以控制蛇动起来了。
然后就是蛇吃食物要增加长度了:
//判断蛇头的坐标是否和食物相等
if (snake.x[0] == food.x && snake.y[0] == food.y) {
snake.currentLen++;
flag = 1;
}
很好理解,当蛇头的xy坐标和食物的xy坐标重合了,蛇身长度就加一了。
flag是我们宏定义,我们用来控制食物生成,因为食物不能你没吃到就在那儿一直生成,吃到一个生成一个。
代码如下:
- int statement() {//判断游戏当前的状态
- //判断蛇头是否撞到墙壁
- if (snake.x[0] == 0 + MOVE_CENTER || snake.x[0] == MAP_WIDTH + MOVE_CENTER || snake.y[0] == 0 || snake.y[0] == MAP_HEIGHT - 1) {
- return 1;
- }
- //蛇头吃到自己身体的任意部位
-
- for (int i = 1; i < snake.currentLen; i++) {
- if (snake.x[0] == snake.x[i] && snake.y[0] == snake.y[i]) {
- return 1;
- }
- }
- return 0;
- }
贪吃蛇肯定不能一直进行下去,怎么游戏结束呢?
两种情况:
一种是蛇撞墙了,二蛇咬到自己身体了
先看第一种:
//判断蛇头是否撞到墙壁
if (snake.x[0] == 0 + MOVE_CENTER || snake.x[0] == MAP_WIDTH + MOVE_CENTER || snake.y[0] == 0 || snake.y[0] == MAP_HEIGHT - 1) {
return 1;
}
怎么判断蛇是否撞到墙壁?用if语句当蛇的x坐标与y坐标与地图边框坐标重合时,就结束游戏。
再看第二种:
for (int i = 1; i < snake.currentLen; i++) {
if (snake.x[0] == snake.x[i] && snake.y[0] == snake.y[i]) {
return 1;
}
}
return 0;
很简单对吧,写个for循环,然后蛇头xy坐标和蛇身xy坐标相等时退出游戏
。
六个模块大功告成,我们来写main函数:
int main() {
createMap();
while (1) {
Sleep(300); //让食物别出现的那么快
if (flag) {
createFood();
}
moveSnake();
if (statement()) {
gotoxy(MAP_WIDTH / 2, MAP_HEIGHT / 2);//在屏幕中央输入game over
printf("Game Over!\n");
//改变光标位置,使其不影响美观
gotoxy(96, 0);
exit(0);
}
}
这个贪吃蛇虽然简陋,但是作为我们初学练手的项目已经有一定难度的,为什么要写这篇文章呢,因为我认为造轮子也要写自己的理解,你不能一天到晚造轮子,轮子是啥问到你头上一问三不知,写出自己的理解才能把知识变成自己的东西。
故我写下了自己的理解和开发过程,希望能对读者理解有所帮助,笔者为正在找实习的大四学生,才能有限,若有建议请不吝赐教。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。