总结出以下几点:

1.需要多次被包含的头文件里不能定义全局变量,否则会报错“重定义”

2.char *strncpy(char *dest, const char *src, int n),

把src所指向的字符串中以src地址开始的前n个字节复制到dest所指的数组中,并返回被复制后的dest。

3.蛇变长现象:虽然我们会将蛇的每个结点的位置向前移动,但是原先蛇的尾节点的背景位置的方块却没有被清除,所以看起来蛇会变长,

  我们只是把新的位置涂黑,而没有把原来被涂黑的位置字符置为空格。

snake.h

 #ifndef SNAKE_H_INCLUDE
#define SNAKE_H_INCLUDE #include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>
#include <conio.h>
#include <string.h>
#include "resource.h" #pragma comment(lib,"winmm.lib") //链接这个库 #define SNAKELENGTH 25
#define false 0
#define true 1 enum { //snake[i][2]的数据,存放了蛇的结点移动方向,
//他们的数值代表方向增量,这样结点向某个方向移动时只需将x或y加上这个增量即可
to_east = ,
to_west = -,
to_north = -,
to_south =
}; // 设置窗口标题
void windowTitle(); //设置窗口大小和颜色
void windowSizeColor(); //封面
void FirstStage(); //播放音乐
void MyPlaySound(unsigned int id); //检测空格键
void TestSpace(); //停止播放音乐
void StopMusic(); //显示背景
void showBackGround(); //为蛇随机生成一个随机位置
void SetSnakeRandPos(); //将蛇的结点画到背景上
void DrawSnake(); //蛇移动
void snakeMove(); //清除蛇尾部的残余阴影
void DestructionOfResidual(); //控制蛇的转向和移动
void snakeWheel(); //判断蛇是否死亡
boolean IsSnakeDie(); boolean IsSnakeDie2(); //随机产生一个食物
void ProduceFood(); //蛇吃食物和变长
void SnakeGrowUp(); //过程分数
void ProcessScore(); //最终成绩
void FinalScore(); //清除+5
void clear(); #endif

snake.c

 #define _CRT_SECURE_NO_WARNINGS    //若无次宏定义,则strncpy()会报错
#include "snake.h" int snakeDir = to_west;
clock_t start, stop;
double duration;
boolean ReproduceFood = true; int g_snakeLength = ; //蛇的长度 int g_grade = ; //分数 int g_nline;
int g_ncol; int g_gradeFlag = ; //过程分数显示标志 char BackGround[][] = {
"███████████████████████\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"█ █\n",
"███████████████████████\n"
}; int snake[SNAKELENGTH][] = { }; //表示蛇的结点,每个结点有三个数据,行号,列号,方向 // 设置窗口标题
void windowTitle()
{
system("title 贪吃蛇");
} //设置窗口大小和颜色
void windowSizeColor()
{
system("mode con cols=46 lines=33"); //窗口的宽高 //0 - 黑色 1 - 蓝色 2 - 绿色 3 - 浅绿色 4 - 红色 5 - 紫色 6 - 黄色 7 - 白色 8 - 灰色
// 9-浅蓝色 10(A)-淡绿色 11(B)-淡浅绿色 12(C)-淡红色 13(D)-淡紫色 14(E)-淡红色 15(F)-亮白色
system("color 6A"); //窗口的颜色, 前面那位6是是背景颜色, 后面的A是前景色 } //封面
void FirstStage()
{
printf("\n\n\n\n");
printf("\t\t《欢迎进入贪吃蛇》\n\n");
printf("\t\t《按空格开始游戏》\n\n");
printf("\t\t《W A S D 控制移动》\n\n\n\n\n");
} //播放音乐
void MyPlaySound(unsigned int id)
{
//常用
//返回值是bool类型,参数一位 播放文件的路径
//第二个参数是NULL, 第三个参数
//PlaySound(path, NULL, SND_FILENAME | SND_LOOP | SND_ASYNC); // 用异步方式播放声音,PlaySound函数在开始播放后立即返回。
//1.wav是相对路径
//E:\\KuGou\\1.wav是绝对路径
//这种方式生成的.exe可执行文件很小,且能方便改动音乐等资源,更灵活 //如果第一个参数是资源文件的ID,那么参数三是SND_RESOURCE
//MAKEINTSOURCE这个宏是把一个数字类型转换成指针类型的宏,用这个宏的主要原因是有的资源是用序号定义的,而不是字符串.
//所以要把数字转换成字符串指针,然后再传递给LoadResource之类的函数,这样才加载了资源.
PlaySound(MAKEINTRESOURCE(id), NULL, SND_RESOURCE | SND_ASYNC); //生成的.exe可执行文件很大,因为他将音乐也整合进去了,且资源文件不能改动,不灵活 } void TestSpace()
{
//检测空格键
char chInput; while ()
{
//chInput = getchar(); //getchar()读取一个字符且将它输出在显示屏上,并且这个函数在结束输入时必须按下enter键,输入流才会开始读取
//_getch()读取一个字符但是不会将它显示在显示屏上,这个函数读取字符不必等到用户按下enter键,用户输入字符后立即读取
chInput = _getch(); //其实如果是中文输入法的话会暂时将读取的字母输出在屏幕上,但是按下enter键后会马上消失
//同步检测
if (' ' == chInput) //将常量放在等号左侧,因为这样如果少些一个等号会报错,可以马上定位的报错的位置
break;
} } void StopMusic()
{
//停止播放
PlaySound(NULL, , );
} //void show1()
//{
// int i, j;
// while (1)
// {
// start = clock(); //用clock()函数检测打印一次所需时间,测试后结果为24ms~53ms,超过20ms,人眼所能识别最小的时间间隔是20ms,所以会造成闪烁现象
// system("cls");
// printf("游戏界面\n");
// for (i = 0; i < 20; i++)
// {
// for (j = 0; j < 23; j++)
// if (1 == BlackBlock[i][j])
// printf("█");
// else
// printf(" ");
// printf("\n");
// }
// stop = clock();
// duration = ((double)(stop - start)) / CLK_TCK;
// printf("duration = %lf\n", duration);
// Sleep(1000); //时间间隔是1000ms,即1s
// }
//} void showBackGround()
{
int i;
////用clock()函数检测打印一次所需时间,测试后结果为0ms~10ms,
//且大多数情况下在0ms ~ 4ms,远小于20ms,所以几乎看不到闪烁现象
start = clock();
for (i = ; i < ; i++)
printf(BackGround[i]);
stop = clock();
duration = ((double)(stop - start)) / CLK_TCK;
printf("duration = %lf\n", duration);
/*Sleep(1000);*/ } //void show3()
//{
// while (1)
// {
// system("cls");
// printf(backGround1);
// Sleep(1000);
// }
//} //为蛇产生一个随机位置
void SetSnakeRandPos()
{
int nx = -;
int ny = -; //产生随机数
//srand()函数包含在time.h头文件中,参数类型时unsigned int
//time(NULL);的返回类型是time_t 也就是int64,所以需要一个强制转换
srand((unsigned int)time(NULL)); //种随机种子 //因为蛇默认起始状态为3节,所以最右边要预留3个位置
nx = rand() % + ; //nx的大小范围为1 ~ 19, rand() % 19范围就是0 ~ 18, +1之后就是1 ~ 19
ny = rand() % + ; //ny的大小范围为1 ~ 18, rand() % 18范围就是0 ~ 17, +1之后就是 1 ~ 18 //初始化蛇的起始三个结点
snake[][] = ny; //表示蛇的结点的行号
snake[][] = nx * ; //表示结点的列号
snake[][] = to_west; //表示这个结点的运动方向 snake[][] = ny;
snake[][] = nx * + ;
snake[][] = to_west; snake[][] = ny;
snake[][] = nx * + ;
snake[][] = to_west; //将这三个结点画到背景上
DrawSnake();
} void DrawSnake()
{
int i;
for (i = ; snake[i][] != ; i++) //因为蛇的每个结点的行号都是初始化为0的,所以如果行号为0,代表这个结点还没有被使用
{
strncpy(&BackGround[snake[i][]][snake[i][]], "█", ); //这个不能用带_s 会出问题 将蛇的结点对应在背景图上的2个位置字符赋值为█
}
} //蛇的移动并判断是否死亡
void snakeMove()
{
//将snake[i]的前一个结点snake[i - 1]的三个数据赋值给它,
//这样第i个结点在背景中的位置就会变成它前一个结点i - 1的位置,看起来好像就是结点移动了一样
int i = SNAKELENGTH - ;
int flag = ; //先把蛇从背景上清除掉
DestructionOfResidual(); for (i; i >= ; i--)
{
//后面还未生成的结点无需赋值,跳过即可
if ( == snake[i][])
{
continue;
}
//虽然我们会将蛇的每个结点的位置向前移动,但是原先蛇的尾节点的背景位置的方块却没有被清除,所以看起来蛇会变长
//我们只是把新的位置涂黑,而没有把原来被涂黑的位置字符置为空格 //这时候处理蛇结点尾部残余阴影的一个方法,即将尾结点在背景的位置置为空格
//if (0 == flag)
//{
// //从尾部删除一个结点,即将尾部结点在背景上的位置字符置位空格
// strncpy(&BackGround[snake[i][0]][snake[i][1]], " ", 2);
// flag = 1;
//} snake[i][] = snake[i - ][];
snake[i][] = snake[i - ][];
snake[i][] = snake[i - ][];
} snake[][] = snakeDir; //处理蛇头
if (to_east == snake[][] || to_west == snake[][])
{ snake[][] += snake[][]; //如果是东西方向,即是左右方向,那么snake结点的x分量加上方向增量
} if (to_north == snake[][] || to_south == snake[][])
{
snake[][] += snake[][]; //如果是南北方向,即上下方向移动,那么snake结点的y分量加上方向增量
} //判断蛇头与食物是否重合
/*if (0 == strncmp(&BackGround[snake[0][0]][snake[0][1]], "★", 2))
ReproduceFood = true;*/
//将蛇画在背景上
DrawSnake(); } //清除蛇尾部的残余阴影
void DestructionOfResidual()
{ //将背景恢复原样,我们只是改变了背景,并没与改变蛇的结点中的数据,所以不影响蛇的下次绘制
int i = ;
for (i; snake[i][] != ; i++)
strncpy(&BackGround[snake[i][]][snake[i][]], " ", );
} void snakeWheel()
{
/*char dir;
dir = _getch();
if ('a' == dir)
snake[0][2] = to_west;
else if ('d' == dir)
snake[0][2] = to_east;
else if ('w' == dir)
snake[0][2] = to_north;
else if ('d' == dir)
snake[0][2] = to_south;*/ //异步检测,高字节非0,低字节为1
if (GetAsyncKeyState('W'))
{
if (snake[][] != to_south) //防止蛇反向掉头
snakeDir = to_north;
}
else if (GetAsyncKeyState('S'))
{
if (snake[][] != to_north)
snakeDir = to_south;
}
else if (GetAsyncKeyState('A'))
{
if (snake[][] != to_east)
snakeDir = to_west;
}
else if (GetAsyncKeyState('D'))
{
if (snake[][] != to_west)
snakeDir = to_east;
} } //判断蛇是否死亡, 死亡返回真,否则返回假
boolean IsSnakeDie()
{
//如果蛇头在它的方向上在走一格就是"█"的话,那么蛇死亡,碰到了边界或咬到了自己
if (to_west == snake[][] || to_east == snake[][])
{
if (strncmp(&BackGround[snake[][]][snake[][] + snake[][]], "█", ) == )
return true;
else
return false;
}
else
{
if ( == strncmp(&BackGround[snake[][] + snake[][]][snake[][]], "█", ))
return true;
else
return false;
} } //下面这种判断蛇头为方块则蛇死亡的方式是错误的,因为蛇头一定是方块
//boolean IsSnakeDie2()
//{
// if (0 == strncmp(&BackGround[snake[0][0]][snake[0][1]], "█", 2))
// return true;
// else
// return false;
//} //随机产生一个食物
void ProduceFood()
{
//int g_nline, g_ncol; //食物的行、列坐标
srand((unsigned int)time(NULL)); //产生一个种子
int i; //判断是都需要产生食物
if (!ReproduceFood) //如果ReproduceFood为假,则不需产生食物,直接返回
{
return;
} //防止蛇结点的位置和食物的位置产生冲突
while ()
{
g_nline = rand() % + ; //食物的行号范围为2 ~ 17,rand()% 16 为 0 ~ 15, + 2就成了 2 ~ 17
g_ncol = rand() % + ; //食物的列号范围为2 ~ 20, rand() % 20 为 0 ~ 18, +2 就成了2 ~ 20
boolean bFlag = false;
for (i = ; snake[i][] != ; i++)
if (snake[i][] == g_nline && snake[i][] == g_ncol)
bFlag = true;
if (!bFlag)
break;
} //将食物绘制在背景上
strncpy(&BackGround[g_nline][g_ncol * ], "★", );
ReproduceFood = false;
} //蛇吃食物和变长
void SnakeGrowUp()
{
int i;
if (g_nline == snake[][] && g_ncol * == snake[][])
{
printf("good!\n");
ReproduceFood = true; //for (i = 0; snake[i][0] != 0; i++); //统计蛇的长度,此处用一个全局变量来统计
if (to_west == snake[g_snakeLength - ][] && to_east == snake[g_snakeLength - ][])
{
snake[g_snakeLength][] = snake[g_snakeLength - ][];
snake[g_snakeLength][] = snake[g_snakeLength - ][] - snake[g_snakeLength - ][];
}
else
{
snake[g_snakeLength][] = snake[g_snakeLength - ][] - snake[g_snakeLength - ][];
snake[g_snakeLength][] = snake[g_snakeLength - ][];
}
snake[g_snakeLength][] = snake[g_snakeLength - ][]; g_snakeLength++; //蛇的长度加1 if (g_gradeFlag)
{
g_gradeFlag = ;
}
else
ProcessScore(); //显示成绩+5
}
} void ProcessScore()
{
strncpy(&BackGround[][], "+5", );
g_grade += ;
} //最终成绩
void FinalScore()
{
COORD rd;
rd.X = ;
rd.Y = ;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), rd);    //这个函数用来设置光标位置
printf("game over!\n");
printf("\t\tYour Final grade is: %d\n\n\n\n\n", g_grade);
} //清除+5
void clear()
{
strncpy(&BackGround[][], " ", );
}

背景的三种实现方式:https://www.cnblogs.com/hi3254014978/p/9782497.html

main.c

 #include "snake.h"

 int main()
{
//设置窗口标题
windowTitle(); //设置窗口大小和颜色
windowSizeColor(); //播放音乐
MyPlaySound(IDR_WAVE1); //封面
FirstStage(); //检测空格键
TestSpace(); //停止播放
StopMusic(); //播放游戏内部音乐
MyPlaySound(IDR_WAVE2); //清屏
system("cls"); //随机生成蛇的位置
SetSnakeRandPos();
//showBackGround(); while ()
{
system("cls");
//游戏界面
printf("游戏界面\n"); //界面闪烁的原因是“如果打印左上角第一个字符到打印右下角最后一个字符时间间隔超过20ms,就会闪烁” //判断蛇是否死亡
if (IsSnakeDie())
{
FinalScore();
break;
} snakeWheel(); //蛇移动并判断蛇是否死亡 //其实是每次循环刷新蛇的位置,造成蛇在动感觉
snakeMove(); ProduceFood(); SnakeGrowUp(); //将印有蛇图案的背景显示出来
showBackGround(); clear(); Sleep();
} system("pause"); //防止程序退出,若无此语句,则音乐无法播放
return ;
}

贪吃蛇(c语言实现)的更多相关文章

  1. 贪吃蛇(C语言版)链表实现

    贪吃蛇 gitee:贪吃蛇C语言版: Snake 蛇的结构 typedef struct Snake { int x; int y; struct Snake *next; }; 游戏开始欢迎界面 / ...

  2. C语言用面向对象的思想写贪吃蛇

    大概一年前这时候,接触C语言一个月,那时候知之甚少,对面向对象只觉”可远观而不可亵玩“,而且会看到很多言论说C语言就是面向过程的语言,C++就是面向对象的语言.不过,不记得什么时候在网上看到过一篇博文 ...

  3. 小项目特供 贪吃蛇游戏(基于C语言)

    C语言写贪吃蛇本来是打算去年暑假写的,结果因为ACM集训给耽搁了,因此借寒假的两天功夫写了这个贪吃蛇小项目,顺带把C语言重温了一次. 是发表博客的前一天开始写的,一共写了三个版本,第一天写了第一版,第 ...

  4. C语言 小游戏之贪吃蛇

    还记得非常久曾经听群里人说做贪吃蛇什么的,那时候大一刚学了C语言,认为非常难,根本没什么思路. 前不久群里有些人又在谈论C语言贪吃蛇的事了,看着他们在做,我也打算做一个出来. 如今大三,经过了这一年半 ...

  5. c语言贪吃蛇详解3.让蛇动起来

    c语言贪吃蛇详解3.让蛇动起来 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 上次 ...

  6. c语言贪吃蛇详解-2.画出蛇

    c语言贪吃蛇详解-2.画出蛇 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 蛇的身 ...

  7. c语言贪吃蛇详解1.画出地图

    c语言贪吃蛇详解-1.画出地图 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识点. 首先 ...

  8. c语言贪吃蛇详解5.GameOver功能与显示成绩

    c语言贪吃蛇详解5.GameOver功能与显示成绩 以前我们已经做出来了一个能吃东西变长的蛇.不过它好像不会死... 现在就来实现一下game over的功能吧. 写个函数判断蛇是否撞到自己或者撞到墙 ...

  9. c语言贪吃蛇详解4.食物的投放与蛇的变长

    c语言贪吃蛇详解4.食物的投放与蛇的变长 前几天的实验室培训课后作业我布置了贪吃蛇,今天有时间就来写一下题解.我将分几步来教大家写一个贪吃蛇小游戏.由于大家c语言未学完,这个教程只涉及数组和函数等知识 ...

  10. 贪吃蛇游戏——C语言双向链表实现

    采用了双向链表结点来模拟蛇身结点: 通过C语言光标控制函数来打印地图.蛇身和食物: /************************** *************************** 贪吃 ...

随机推荐

  1. ALTER语句重命名,重新定义和重新排序列

    该CHANGE,MODIFY和 ALTER子句可以改变现有列的名称和定义.他们有这些比较特征: CHANGE: 可以重命名列并更改其定义,或两者都可以. 具有更多的能力MODIFY,但是以某些操作的便 ...

  2. Ubantu-Nginx部署

    nginx+uwsgi+django部署流程   当我们在用django开发的web项目时,开发测试过程中用到的是django自带的测试服务器,由于其安全及稳定等性能方面的局限性,django官方并不 ...

  3. cygwin 解压 tar.xz压缩包

    今天第一次接触到Cygwin,啊,不懂Linux,, 解压分为三个步骤. 第一步,进入压缩包所在的文件目录: cd e:\ >(左边会弹出这个符号,我以为后面的解压要在这里写,其实不是,要再按一 ...

  4. 笔记:Oracle查询重复数据并删除,只保留一条记录

    1.查找表中多余的重复记录,重复记录是根据单个字段(Id)来判断 select * from 表 where Id in (select Id from 表 group byId having cou ...

  5. Threejs着色器基本使用样例改造

    <!DOCTYPE html> <html lang="en"> <head> <title>three.js webgl - bu ...

  6. 安卓 dex 通用脱壳技术研究(一)

    注:以下4篇博文中,部分图片引用自DexHunter作者zyqqyz在slide.pptx中的图片,版本归原作者所有: 0x01 背景介绍 安卓 APP 的保护一般分为下列几个方面: JAVA/C代码 ...

  7. php实现弱语言底层原理分析(转)

    php中弱语言类型的底层实现 PHP是弱语言类型,主要分为三类: 1.标量类型:integer.string.float.boolean 2.复合类型:array.object 3.特殊类型:reso ...

  8. 【Leetcode】292. Nim Game

    problem 292. Nim Game solution class Solution { public: bool canWinNim(int n) { ; } }; 来generalize一下 ...

  9. Sublime 修改快捷键

    Preferences-Key Bindings-User 添加一行: { "keys": ["ctrl+d"], "command": & ...

  10. xdoj-1298(模拟--简易SQL解释器)

    题目链接 一 知识点: 1  substr有2种用法:       假设:string s = "0123456789";      string sub1 = s.substr( ...