2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 完整版! 大一新手C语言数据结构实现简单贪吃蛇游戏

完整版! 大一新手C语言数据结构实现简单贪吃蛇游戏

时间:2024-04-30 14:59:33

相关推荐

完整版! 大一新手C语言数据结构实现简单贪吃蛇游戏

大一新手C语言数据结构实现简单贪吃蛇游戏含源代码

贪吃蛇1.前言简述2.编写语言及工具3.游戏的头文件代码(.h)3.1map.h3.2snake.h3.3Food.h 4.游戏的源文件代码(.c)4.1.地图模块(map.c)4.2.蛇模块(snake.c)4.3.食物模块(Food.c)4.4.主函数模块(main.c) 5.游戏中知识点分析5.1关于线程与进程5.2光标定位函数5.3链表实现蛇的创建和移动5.4MainLop实现键盘控制方向5.5随机食物的出现5.6Sleep函数控制速度 结尾

贪吃蛇

1.前言简述

学习编程快一年了,偶然看到网上有做贪吃蛇的视频,于是便一时兴起,用两天时间跟着视频做了起来,在制作过程中也遇到不少问题和陌生的知识,因此通过想博客与大家分享,讨论。(游戏虽然做出来了,但笔者自己也有很多问题一知半解,如有解释不当,欢迎各位解答指正!)

2.编写语言及工具

语言:C语言 工具:Visual Studio

3.游戏的头文件代码(.h)

注:整个游戏包含7个头文件(3个.h文件,4个.c文件)

.h文件 中一般放的是同名.c文件中定义的变量、数组、函数的声明,声明后.c文件可以直接使用。.c文件一般放的是变量、数组、函数,无需再重复定义。使用.h文件和.c文件的原因主要是为了解决文件编译时重复声明的问题,相对来说使代码结构更清晰(改bug也更快)。

3.1map.h

#pragma once//这个pragma相当于编译指示,我是通过vs的类导向功能同时创建.h和.c文件自动出现的,不加上面这句也可以enum{row = 26, col = 26};//初始化二维数组void InitWall();//输出边界墙void DrawWall();//设置场景中的字符void SetWall(int x, int y, char key);//获取场景中的字符char GetWall(int x, int y);//字符二维数组char GameArray[row][col];//移动光标打印字符void PrintChar(int x,int y,char key);

3.2snake.h

#pragma once//#define _CRT_SECURE_NO_WARRINGS#include <stdio.h>#include <stdlib.h>#include <string.h>//定义结点结构体struct Point{int x;int y;struct Point* next;};//定义运动方向enum EDiection{up='w',down='s',left='a',right='d'};//初始化蛇void InitSnake();//销毁蛇void DestroyPoint();//添加结点void AddPoint(int x, int y);//蛇头指针struct Point* pHead;//删除结点void DelPoint();enum EDiection direction;//蛇头的坐标,这个决定了游戏是否继续,非常重要!!!!!int x;int y;

3.3Food.h

#pragma oncevoid SetFood();int FoodX;int FoodY;

4.游戏的源文件代码(.c)

4.1.地图模块(map.c)

#include "map.h"#include <stdio.h>#include <stdlib.h>#include <string.h>#include <Windows.h>//初始化二维数组void InitWall(){for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){if (i == 0 || j == 0 || i == row - 1 || j == col - 1){GameArray[i][j] = '*';//边界围墙}else{GameArray[i][j] = ' ';//活动区域}}}}//输出边界墙void DrawWall(){for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){printf("%c ", GameArray[i][j]);}switch (i){case 5:printf("贪吃蛇游戏"); break;case 9:printf("操作提示:"); break;case 11:printf("上: w"); break;case 13:printf("下: s"); break;case 15:printf("左: a"); break;case 17:printf("右: d"); break;default:break;}printf("\n");}}//设置场景中的字符void SetWall(int x, int y, char key){GameArray[x][y] = key;}//获取场景中的字符char GetWall(int x, int y){return GameArray[x][y];}//移动光标定位void GotoXY(HANDLE hout,int x,int y){COORD pos;pos.X = x;pos.Y = y;SetConsoleCursorPosition(hout,pos);}//移动光标打印字符void PrintChar(int x, int y, char key) {HANDLE ss = GetStdHandle(STD_OUTPUT_HANDLE);GotoXY(ss, x, y);printf("%c", key);}

4.2.蛇模块(snake.c)

#include "snake.h"#include "map.h"#include <stdio.h>#include <stdlib.h>#include <string.h>//初始化蛇void InitSnake(){pHead = NULL;direction = right;//数组的坐标AddPoint(5,3);AddPoint(5,4);AddPoint(5,5);AddPoint(5,6);}//添加结点void AddPoint(int x, int y){//创建新的结点struct Point* NewPoint = (struct Point*)malloc(sizeof(struct Point));if (NewPoint == NULL) {//判断结点是否创建成功return;}//新结点赋值对应的x,y坐标NewPoint->x = x;NewPoint->y = y;NewPoint->next = NULL;if (pHead != NULL){//pHead != NULL则为老蛇头存在,将其改变为身体SetWall(pHead->x, pHead->y, '=');PrintChar(pHead->y * 2, pHead->x, '='); }//NewPoint作为新的蛇头pHead,老蛇头被NewPoint—>next指向NewPoint->next = pHead;pHead = NewPoint;//数组的坐标为y为横坐标,x为纵坐标,往下x增加,往右是y增加//光标的坐标为x为横坐标,y为纵坐标,往下y增加,往右是x增加SetWall(pHead->x, pHead->y, '@');PrintChar(pHead->y * 2, pHead->x, '@');//销毁蛇void DestroyPoint(){struct Point* cur = pHead;while (pHead != NULL){cur = pHead->next;free(cur);pHead = cur;}}//删除结点void DelPoint(){//两个结点以上才能删除if (pHead == NULL || pHead->next == NULL){return;}struct Point* pre = pHead;struct Point* cur = pHead->next;while (cur->next!=NULL){pre = pre->next;cur = pre->next;}SetWall(cur->x, cur->y, ' ');PrintChar(cur->y * 2, cur->x, ' ');free(cur);cur = NULL;pre->next = NULL;}}

4.3.食物模块(Food.c)

#include <time.h>//设置食物void SetFood(){srand((unsigned int)time(NULL));while (1){FoodX = rand() % (row - 2) + 1;FoodY = rand() % (col - 2) + 1;if (GetWall(FoodX, FoodY) == ' '){SetWall(FoodX, FoodY,'$');PrintChar(FoodY * 2, FoodX, '$');break;}}}

4.4.主函数模块(main.c)

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <Windows.h>#include <process.h>#include "map.h"#include "snake.h"#include "Food.h"char ch = NULL;void MainLop(){char prekey = NULL;while (1){ch =_getch();//如果是蛇没有运动if (ch == NULL && prekey == NULL){continue;}//如果输入方向恰和蛇运动方向相反if((ch == up && prekey == down)||(ch == down && prekey == up)||(ch==left&&prekey==right)||(ch == right && prekey == left)){ch = prekey;}else{prekey = ch;}switch (ch){case 'w':direction = up; break;case 's':direction = down; break;case 'a':direction = left; break;case 'd':direction = right; break;default:break;}}}//初始化分数int score = 0;//初始化速度int vspace = 200;void main(){InitWall();DrawWall();SetFood();InitSnake();//开启线程HANDLE h;h = (HANDLE)_beginthread(MainLop,0,NULL);while (1){//开始蛇不会移动,按任意键启动游戏if (ch == NULL) {continue;}//通过判断分数提升关卡难度switch (score){case 5:vspace = 100; break;case 10:vspace = 70;break;default:break;}Sleep(vspace);x = pHead->x;y = pHead->y;switch (direction){case up:x--;break;case down:x++;break;case left:y--;break;case right:y++;break;default:break;}//若蛇头碰到墙或者自己身体游戏结束if (GetWall(x, y) == '*' || GetWall(x, y) == '='){break;}//若吃到食物if (GetWall(x, y) == '$'){score++;AddPoint(x, y);//再设置新的食物SetFood();}else//正常移动{AddPoint(x, y);DelPoint();}}PrintChar(0, row, ' ');printf("最终得分为:\t%d分\n", score);system("pause");}

5.游戏中知识点分析

前面只放了源代码没有解释,该部分主要针对上面的代码值得推敲的地方,再重新拿出来分析。同时在这部分代码分析中有些地方笔者解释的不够透彻,所以放上链接帮助大家深入了解学习。

5.1关于线程与进程

首先,本游戏要对线程进程有一定了解。

关于进程和线程的概念,可参照:

链接: C语言多线程编程-进程和线程的基本概念.

在贪吃蛇游戏里,也采用了多线程编程,首先由main函数开启一个进程,然后在一个进程里再分出两个线程。

线程1:蛇的移动。

线程2:通过键盘控制蛇的方向

注:线程也不能多个同时执行,多线程程序也是由cpu轮流执行的,cpu在各线程里来回执行的速度很快,从用户角度上像是几个线程同时执行。

5.2光标定位函数

目的:确定蛇的坐标,为蛇在屏幕的显示和移动做准备。

核心代码:

//一定要导入该头文件!#include <Windows.h>void GotoXY(HANDLE hout,int x,int y){COORD pos;pos.X = x;pos.Y = y;SetConsoleCursorPosition(hout,pos);}void PrintChar(int x, int y, char key) {HANDLE ss = GetStdHandle(STD_OUTPUT_HANDLE);GotoXY(ss, x, y);printf("%c", key);}

代码分析:首先介绍两个结构体HANDLE和COORD

COORD是描述位置的结构体,包含x, y坐标

typedef struct _COORD {SHORT X;SHORT Y;} COORD, *PCOORD;

HANDLE

HANDLE是一种指向结构体的指针,同时也是处理对象的接口,也称句柄,你可以通过句柄来操作程序所涉及的对象。

SetConsoleCursorPosition函数

功能:设置控制台光标坐标实现定位

参数:第一个参数类型为HANDLE,第二个参数类型为COORD

GetStdHandle函数

功能:获取指定的标准设备的句柄

参数:有三种可能的参数取值

STD_INPUT_HANDLE—标准输入句柄

STD_OUTPUT_HANDLE—标准输出句柄

STD_ERROR_HANDLE—标准错误句柄

注:GetStdHandle函数的返回值必须由HANDLE类型的变量接收。且标准输出均是通过屏幕

综上小结 PrintChar函数:

参数:打印字符的x坐标、y坐标、打印的字符。

过程:PrintChar函数先定义了HANDLE类型的ss接收GetStdHandle函数的标准输出句柄。再调用GotoXY函数传入参数x、y坐标和HANDLE指针,GotoXY函数再调用SetConsoleCursorPosition函数确定打印位置坐标,就实现了蛇在屏幕上的打印。

相关知识的学习来源:控制台API函数----HANDLE、SetConsoleCursorPosition、SetConsoleTextAttribute

5.3链表实现蛇的创建和移动

整个蛇的创建和移动都是通过链表实现的。

//蛇由一个个结点组成,这里先定义结点struct Point{int x;int y;struct Point* next;};//蛇头的坐标,这个决定了游戏是否继续,非常重要!!!!!int x;int y;//蛇头指针struct Point* pHead;

先将蛇打印在屏幕上

添加结点的函数上面有源代码就不列出了

//初始化蛇void InitSnake(){pHead = NULL;direction = right;//数组的坐标AddPoint(5,3);AddPoint(5,4);AddPoint(5,5);AddPoint(5,6);}

实现蛇的移动

实现蛇的的移动是在主函数中不断调用创建和销毁结点来实现

1.创建结点

//添加结点void AddPoint(int x, int y){//创建新的结点struct Point* NewPoint = (struct Point*)malloc(sizeof(struct Point));if (NewPoint == NULL) {//判断结点是否创建成功return;}//新结点赋值对应的x,y坐标NewPoint->x = x;NewPoint->y = y;NewPoint->next = NULL;if (pHead != NULL){//pHead != NULL则为老蛇头存在,将其改变为身体SetWall(pHead->x, pHead->y, '=');PrintChar(pHead->y * 2, pHead->x, '='); }//NewPoint作为新的蛇头pHead,老蛇头被NewPoint—>next指向NewPoint->next = pHead;pHead = NewPoint;//数组的坐标为y为横坐标,x为纵坐标,往下x增加,往右是y增加//光标的坐标为x为横坐标,y为纵坐标,往下y增加,往右是x增加SetWall(pHead->x, pHead->y, '@');PrintChar(pHead->y * 2, pHead->x, '@');}

2.删除结点

//删除结点void DelPoint(){//两个结点以上才能删除if (pHead == NULL || pHead->next == NULL){return;}struct Point* pre = pHead;struct Point* cur = pHead->next;while (cur->next!=NULL){pre = pre->next;cur = pre->next;}SetWall(cur->x, cur->y, ' ');PrintChar(cur->y * 2, cur->x, ' ');free(cur);cur = NULL;pre->next = NULL;}

主函数中用调用添加和删除即可实现移动效果

//添加一个新结点并在尾部结点删除一个即可实现AddPoint(x, y);DelPoint();

注:该部分为数据结构的链表知识

5.4MainLop实现键盘控制方向

目的:通过键盘输入字符控制贪吃蛇的运动方向

核心代码:

考虑到输入模块只有上下左右键,因此定义为枚举类型,名为EDiection(snake.h)

//在snake.h中定义枚举类型的上下左右enum EDiection{up='w',down='s',left='a',right='d'};//枚举类型的变量命名directionenum EDiection direction;

接下来在主函数中开启控制蛇方向的线程

//主函数文件要引入<process.h>文件//开启线程HANDLE h;h = (HANDLE)_beginthread(MainLop,0,NULL);

_beginthread参数分别为:函数的入口地址,栈大小, 参数列表。接下来编写入口函数MainLop:

(至于栈大小,参数列表为何是0和NULL,笔者还没查到,知道的朋友欢迎留言)

void MainLop(){//prekey是记录上一次蛇运动的方向char prekey = NULL;while (1){ch =_getch();//如果是蛇没有运动if (ch == NULL && prekey == NULL){continue;}//如果输入方向恰和蛇运动方向相反if((ch == up && prekey == down)||(ch == down && prekey == up)||(ch==left&&prekey==right)||(ch == right && prekey == left)){ch = prekey;}else{prekey = ch;}//switch语句是通过键盘输入改变运动方向switch (ch){case 'w':direction = up; break;case 's':direction = down; break;case 'a':direction = left; break;case 'd':direction = right; break;default:break;}}}

注:由于调试过程中存在bug:在蛇运动时按与蛇运动方向相同的键会结束游戏,而且刚开始游戏时蛇就自动跑了起来,最好添加个开始键,因此加入if-else语句,作用是将上一次运动的方向缓存在prekey指针里,和本次按键判断,具体可参照上述代码。

5.5随机食物的出现

#include <time.h>//设置食物void SetFood(){srand((unsigned int)time(NULL));while (1){FoodX = rand() % (row - 2) + 1;FoodY = rand() % (col - 2) + 1;if (GetWall(FoodX, FoodY) == ' '){SetWall(FoodX, FoodY,'$');PrintChar(FoodY * 2, FoodX, '$');break;}}}

这里引入了一个随机数的生成器来随机设定x,y的坐标。

也可以参见这篇文章帮助理解。

链接: srand((unsigned)time(NULL))详解.

5.6Sleep函数控制速度

为了额外给游戏制造难度,关卡:“速度随蛇长增加而增加”。

引入了Sleep函数, 功能: 执行挂起一段时间,也就是等待一段时间在继续执行。主函数里每次调用Sleep即通过改变结点函数的速度控制蛇速度的作用。

注:使用Sleep函数需要引入windows.h头文件

核心代码:

//初始化分数int score = 0;//初始化速度int vspace = 200;void main(){InitWall();DrawWall();SetFood();InitSnake();//开启线程HANDLE h;h = (HANDLE)_beginthread(MainLop,0,NULL);while (1){//开始蛇不会移动,按任意键启动游戏if (ch == NULL) {continue;}//通过判断分数提升关卡难度switch (score){case 5:vspace = 100; break;case 10:vspace = 70;break;default:break;}//这里分数越高,vspace越高,速度越快Sleep(vspace);

对Sleep函数的详细解释:

链接: 【C/C++】Sleep函数的用法.

结尾

以上就是贪吃蛇游戏的全部实现过程了,大家有什么问题或者发现什么错误都欢迎评论区留言,如果喜欢就分享收藏点赞,如果能您能打赏,真的对我非常非常有帮助!感谢支持!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。