写在开始
上一篇博客的链接为:用C实现贪吃蛇,看看你的C学得怎样?四
任务清单
本次项目的任务清单暂定如下:
1.创建地图和起始界面(已完成)
2.创建蛇,食物、超级食物投放(已完成)
3.通过键盘控制蛇的移动(已完成)
4.基本功能(蛇吃食物,蛇的增长等)(已完成)
5.判断游戏是否结束(已完成)
6.计算游戏时长
7.加入最高纪录功能
8.游戏等级进度
9.游戏重新开始
代码实现
//1.定义头文件
#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
//2.宏定义声明
#define X 23 //定义地图大小
#define Y 50
#define UP 0 //0为上,1为下,2为左,3为右
#define DOWN 1
#define LEFT 2
#define RIGHT 3
char map[X][Y]; //地图数组
int s[X*Y][2]; //蛇身坐标数组
int sLength; //蛇的长度
int direction; //蛇的方向
bool eated = false; //标记是否吃到食物
bool GameOver = false; //判断游戏是否结束
int score = 0; //得分
int level = 1;
int delay = 250; //通过延迟控制蛇的行进速度
clock_t start, finish; //定义计时函数变量
double duration;
//3.调用函数声明
void init() //程序开始时的初始化操作
{
srand((unsigned)time(NULL)); //设置随机数种子为现在的时间
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info); //隐藏关标
int i, j;
for (i = 0; i<X; i++)
{
map[i][0] = 1; //让第一列为1
map[i][Y - 1] = 1; //让最后一列为1
}
for (j = 0; j<Y; j++)
{
map[0][j] = 1; //让第一行为1
map[X - 1][j] = 1; //让最后一行为1
}
sLength = 4; //让蛇的最初长度为4
s[0][0] = X / 2;
s[0][1] = Y / 2; //给蛇头坐标赋值
for (i = 1; i<4; i++)
{
s[i][0] = s[0][0] + i;
s[i][1] = s[0][1]; //给刚开始的蛇身几个初始坐标
}
direction = UP;
}
void gotoxy(int i, int j) //移动光标
{
COORD position = { j, i };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
void menu()//初始菜单界面
{
printf("\n\n\n\n\n\n\t\t\t********************贪吃蛇(实验版)******************\t\t\t\n\n");
printf("\t\t\t********************欢迎参与游戏测试******************\t\t\t\n\n");
printf("\t\t\t********************按任意键开始游戏******************\t\t\t\n\n");
printf("\t\t\t\t\t 职业工具人\n\n");
printf("\t\t\t\t\t VERSION:V1.7\n\n");
printf("\t\t\t\t请先确保处于英语输入法,否则无法进行游戏");
_getch();
}
void draw_map() //创建地图
{
gotoxy(0, 0);
int i, j;
for (i = 0; i<X; i++)
{
for (j = 0; j<Y; j++) //两重for循环遍历数组
{
if (map[i][j] == 0) //为0输出空格
printf(" ");
else if(map[i][j]==1) //为1输出#
printf("#");
}
printf("\n"); //别忘了换行
}
}
int check(int ii, int jj) //判断这个点能不能放食物,可以放返回1,不能放返回0
{
if (map[ii][jj] == 1) //如果有障碍物,返回0
return 0;
int i;
for (i = 0; i<sLength; i++)
{
if (ii == s[i][0] && jj == s[i][1]) //如果和其中一个蛇身重合,就返回0
return 0;
}
if (ii == 0 || ii == X - 1 || jj == 0 || jj == Y - 1) //如果在边界上面,返回0
return 0;
if (map[ii][jj] == -1) //若该点有食物,则不生成超级食物
return 0;
return 1; //最后筛选 过后的是符合条件的点
}
void food() //生成食物
{
int i, j;
do
{
i = rand() % X; //生成0~X-1之间的一个数
j = rand() % Y; //生成0~Y-1之间的一个数
}
while (check(i, j) == 0); //生成点直到满足条件
map[i][j] = -1; //标记为食物
gotoxy(i, j);
printf("*"); //画出食物
}
void s_food() //生成超级食物
{
int i, j;
do
{
i = rand() % X;
j = rand() % Y;
}
while (check(i, j) == 0);
map[i][j] = 2;
gotoxy(i, j);
printf("$");
}
void draw_snake() //画蛇
{
int i;
for (i = 0; i<sLength; i++)
{
gotoxy(s[i][0], s[i][1]); //移动关标到蛇的坐标
printf("@"); //在这个位置画蛇
}
}
void move() //控制蛇的移动
{
int i;
gotoxy(s[sLength - 1][0], s[sLength - 1][1]);
printf(" "); //在尾巴上面画空格以擦除尾巴
for (i = sLength - 1; i > 0; i--) //从尾巴开始,每一个点的位置等于它前面一个点的位置
{
s[i][0] = s[i - 1][0];
s[i][1] = s[i - 1][1];
}
switch (direction)
{
case UP:
s[0][0]--;
break;
case DOWN:
s[0][0]++;
break;
case LEFT:
s[0][1]--;
break;
case RIGHT:
s[0][1]++;
break;
}
}
bool gameover() //判断是不是结束游戏
{
int sX = s[0][0], sY = s[0][1]; //蛇头的x坐标和y坐标
if (sX == 0 || sX == X - 1 || sY == 0 || sY == Y - 1)
GameOver = true;
for (int i = 1; i<sLength; i++) //判断有没有吃到自己
{
if (s[i][0] == sX && s[i][1] == sY)
GameOver = true;
}
return GameOver;
}
int game_level() //速度等级构建
{
int Level = score / 100 + 1;
return Level;
}
void clear_food() //清除地图数组中由于食物被修改的值
{
int i, j;
for (i = 0; i < X;i++)
for (j = 0; j < Y; j++)
{
if (map[i][j] == -1 || map[i][j] == 2)
map[i][j] = 0;
}
}
void redraw_snake() //重置蛇的位置
{
int i;
sLength = 4;
s[0][0] = X / 2;
s[0][1] = Y / 2; //给蛇头坐标赋值
for (i = 1; i<4; i++)
{
s[i][0] = s[0][0] + i;
s[i][1] = s[0][1]; //给刚开始的蛇身几个初始坐标
}
direction = UP;
}
void replay() //一键重玩
{
system("cls");
clear_food();
draw_map();
redraw_snake();
food();
s_food();
score = 0; //重置数据
delay = 250;
level = 1;
}
void key() //定义key,用于接受按键键值
{
if (_kbhit() != 0) //如果有键盘输入
{
char in;
while (!_kbhit() == 0) //如果玩家输入了多个按键,以最后一个按键为准
in = _getch();
switch (in)
{
case 'w':
case 'W':
if (direction != DOWN) //不能后退
direction = UP;
break;
case 's':
case 'S':
if (direction != UP)
direction = DOWN;
break;
case 'a':
case 'A':
if (direction != RIGHT)
direction = LEFT;
break;
case 'd':
case 'D':
if (direction != LEFT)
direction = RIGHT;
break;
case 'p':
case 'P':
gotoxy(X, 0); //将光标移动到下面
system("pause");
gotoxy(X, 0);
printf(" "); //消去下面的按任意键继续
break;
case 'r':
case 'R':
replay();
break;
case ' ':
if (delay == 50)
return;
else
delay -= 10;
break;
}
}
}
void print() //显示各项操作快捷键和得分
{
gotoxy(0, Y+ 2);
printf("贪吃蛇实验版");
gotoxy(1, Y + 2);
printf("测试中……");
gotoxy(4, Y + 2);
printf("w-->上");
gotoxy(5, Y + 2);
printf("s-->下");
gotoxy(6, Y + 2);
printf("a-->左");
gotoxy(7, Y + 2);
printf("d-->右");
gotoxy(8, Y + 2);
printf("p-->暂停");
gotoxy(9, Y + 2);
printf("r-->重玩");
gotoxy(10, Y + 2);
printf("空格-->加速");
gotoxy(12, Y + 2);
printf("得分:%d", score);
gotoxy(13, Y + 2);
printf("等级:%d", game_level());
}
void s_time() //起始时间
{
start = clock();
}
void e_time() //结束时间
{
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC; //计算持续时间
printf("游戏时长:%f秒\n ", duration);
}
void getHighScore(int * highscore)
{
FILE * fp = NULL; //文件指针,指向成功打开的文件
//打开文件,以只读模式
fp = fopen("highscore.txt", "r");
//如果文件不存在,以写入模式打开文件
if (fp == NULL)
{
fp = fopen("highscore.txt", "w");
fprintf(fp, "%-10d", *highscore);
}
else
fscanf(fp, "%10d", highscore);
}
void storeHighScore(int highscore)
{
FILE * fp = NULL;//文件指针,指向成功打开的文件
fp = fopen("highscore.txt", "w");//文件只读模式
fprintf(fp, "%-10d", highscore);
fclose(fp);
}
void high_score()
{
int highscore = 0; //存储最高分
//获取历史最高分
getHighScore(&highscore);
printf("Original high score: %d\n\n", highscore);
if (score > highscore)
highscore = score;
storeHighScore(highscore);
}
void restart() //结束后重新开始
{
GameOver = false;
replay();
}
//4.主函数体
int main()
{
init(); //程序开始时的初始化操作
menu();
s_time();
system("cls"); //清除屏幕内容
draw_map(); //画地图
food(); //画食物
s_food();
loop:
{
while (1)
{
draw_snake(); //画蛇
print();
Sleep(delay); //等待一段时间
key();
move(); //移动蛇(主要是修改蛇身数组的数据)
if (map[s[0][0]][s[0][1]] == -1) //如果蛇头碰到食物,就重新投放食物,并且把食物点重置为0
{
eated = true; //标记已经吃到食物
score += 20;
sLength++;
if (game_level() != level)
{
delay -= 10;
level++;
}
eated = false; //设置为false,不然无限变长
food();
map[s[0][0]][s[0][1]] = 0; //去掉食物
}
if (map[s[0][0]][s[0][1]] == 2)
{
eated = true;
score += 50;
sLength += 2;
if (game_level() != level)
{
delay -= 10;
level++;
}
eated = false; //设置为false,不然无限变长
s_food();
map[s[0][0]][s[0][1]] = 0;
}
if (gameover())
{
system("cls"); //清除屏幕内容
printf("Game Over\n");
printf("最终得分:%d\n\n", score);
e_time();
high_score();
break;
}
}
}
printf("双按空格退出,双按r键重玩\n");
if (_getch() == ' ')
return 0;
else if (_getch()=='r'||_getch()=='R')
{
restart();
goto loop;
}
}
总结
本次的博客将最后的四个功能点都实现了,对于刚学完C的小伙伴来说,比较复杂的功能点是获取最高分写入文件,运用了指针以及文件操作。指针是C的灵魂,也是最难懂得部分,博主目前也没能够完全地悟透,在这里就借助了一部分百度的力量实现了这个功能。
此外,在重玩的部分也有点不太优雅,使用了goto关键词,其实这个关键字是在开发中被禁止使用的,但是博主能力有限,没能够避开goto来实现的方法,如果有想法的小伙伴,可以在留言区里提供下思路,帮助博主重构gameover后重玩游戏的算法!
结语
如果说这篇文章有让你学到一定的知识的话,不妨点个赞和关注,让博主能够看到,如果讲解中有什么错误和疏忽,也劳烦在评论中指出或提问,博主会第一时间进行更新和答复,谢谢!