C语言 贪吃蛇
感谢博友的文章,本文章借鉴了博友“隔壁的二大爷”的思路,小白入门——“贪吃蛇”的C语言实现(详细)
平常编写C语言代码都是在Console控制台进行调试,想要实现复杂的可视化操作需要引进其他的头文件。网上大部分利用C语言实现贪吃蛇都引入了graphic.h头文件,受限于本人技术有限,在dev-c++环境中没有测试成功。后来看到了博友隔壁二大爷的文章,利用window.h提供的光标移动函数可以模拟出蛇的动画效果
效果
下图是程序运行的效果图:
调试环境
- 编译环境: Dev-C++ TDM-GCC 9.2.0 64位
- 环境配置: 游戏添加了背景音乐,引入<mmsystem.h>头文件,在编译选项中需要加入编译条件: 工具 -> 编译选项 第二行中加入 -lwinmm 如下图
程序需要完成工作
- 初始化工作
- 随机食物
- 蛇的移动
- 蛇的增长
- 游戏结束
下面就以程序来讲解各项工作的实现方法
头文件
#include <stdio.h>
#include <stdlib.h>
#include <windows.h> //光标移动
#include <time.h> //食物随机使用到时间函数
#include <conio.h> //键盘事件获取
#include <mmsystem.h> //音乐播放函数
数据定义
#define false 0
#define true 1
//坐标结构体
typedef struct Point {
int x;
int y;
} Point;
//蛇的结构体
typedef struct Snaker {
Point pos;
struct Snaker *next;
} Snake;
// 定义蛇头
Snake *snakeHead = NULL;
//食物结构体
typedef struct Food {
Point pos;
} Food;
Food food;
//键盘方向,space是初始状态
enum Dirt {
right = 77,
left = 75,
up = 72,
down = 80,
space = 0
} dirt;
void startGame(); //游戏开始界面
void gameOver(); //游戏结束界面
void drawGraph(); //画背景
void gotoXY(int x, int y); //到指定坐标
void gotoPrint(int x, int y); //图标输出
void gotoDelete(int x, int y); //图标删除
void drawFood(); //食物产生
int keyPress(); //键盘操作
int Judge(); //游戏状态
void snakerMove(); //蛇移动
void snakerEating(); //蛇吃食物
void snakerPostion(); //蛇的坐标
void freeLink(); //删除蛇链表
void playBgMusic(); //播放背景音乐
char name[256]; //玩游戏输入的名字
int score = 0; //记录分数
int count = 0; //蛇的长度
int speed = 150; //蛇行驶速度
函数定义
- 光标移动函数(gotoXY)
//该代码借鉴了上面提到的博友的代码,有兴趣的可以到原博主页面研究研究
void gotoXY(int x, int y) {
//更新光标位置
COORD pos;
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
// 隐藏光标
CONSOLE_CURSOR_INFO cursor;
cursor.bVisible = FALSE;
cursor.dwSize = sizeof(cursor);
SetConsoleCursorInfo(handle, &cursor);
}
- 图标打印函数(gotoPrint)
//光标移动到指定位置,打印图标
void gotoPrint(int x, int y) {
gotoXY(x, y);
printf("■");
}
- 图标删除函数(gotoDelete)
void gotoDelete(int x, int y) {
gotoXY(x, y);
printf(" "); //输出空格
}
- 主函数:
int main(int argc, char *argv[]) {
dirt = space;
playBgMusic();
system("color 0B");
startGame();
drawGraph();
drawFood();
if (keyPress() == 0)
return 0;
return 0;
}
- 初始化背景音乐(playBgMusic)
void playBgMusic() {
//调用mciSendString实现背景音乐播放, tsc.mp3 为软件同目录下的音乐文件
mciSendString(TEXT("open tcs.mp3 alias bkmusic"), NULL, 0, NULL);
mciSendString(TEXT("play bkmusic repeat"), NULL, 0, NULL);
}
- 游戏开始界面初始化(startGame)
void startGame() {
gotoXY(15, 10);
printf("/*************************************/");
gotoXY(15, 20);
printf("/*************************************/");
gotoXY(20, 13);
printf("Welcome to the game of snaker");
gotoXY(20, 15);
printf("↑ → ↓ ← control position");
gotoXY(20, 18);
printf("Please enter your name:"); //输入用户名
scanf("%s", name);
system("cls"); //清除屏幕
}
初始化的界面如下,需要输入用户名才能进行进入下一个页面
- 描述背景(drawGraph)
void drawGraph() {
// 背景上下的围挡
for (int i = 0; i < 58; i += 2) {
gotoPrint(i, 0);
gotoPrint(i, 26);
}
//背景左右的围挡
for (int i = 0; i < 27; i++) {
gotoPrint(0, i);
gotoPrint(58, i);
}
//右侧提示得分信息
gotoXY(63, 10);
printf("hello %s, have fun!", name);
gotoXY(63, 15);
printf("Your Score Is: %d", score);
//初始化蛇,默认三个方块,设定默认的初始位置,蛇头在最上方
snakeHead = (Snake *)malloc(sizeof(Snake));
Snake *body1 = (Snake *)malloc(sizeof(Snake));
Snake *body2 = (Snake *)malloc(sizeof(Snake));
snakeHead->pos.x = 16;
snakeHead->pos.y = 15;
body1->pos.x = 16;
body1->pos.y = 16;
body2->pos.x = 16;
body2->pos.y = 17;
snakeHead->next = body1;
body1->next = body2;
body2->next = NULL;
//打印蛇
gotoPrint(snakeHead->pos.x, snakeHead->pos.y);
gotoPrint(body1->pos.x, body1->pos.y);
gotoPrint(body2->pos.x, body2->pos.y);
count = 3;
}
游戏的界面如下图,左侧是游戏界面,右侧是信息提示界面,实时更新用户的得分。
- 随机食物函数(drawFood
食物的随机使用C语言的rand函数,使用rand函数前需要调用srand函数,否则随机出来的数据都是相同的。 另外,随机食物需要考虑食物的坐标是否在蛇的身上,如果出现在蛇的身上需要重新随机,直到食物位置和蛇不重叠。 可以看到上图@为随机出现的食物
/*
创建食物,要判断是否与蛇重叠
*/
void drawFood() {
char flag = false;
while (!flag) {
flag = true;
srand((int)time(NULL));
food.pos.x = rand() % 50 + 2;
food.pos.y = rand() % 24 + 1;
if (food.pos.x % 2 != 0) {
food.pos.x = food.pos.x + 1;
}
//检查是否与蛇重叠
Snake *judge = snakeHead;
while (judge) {
if ((food.pos.x == judge->pos.x)
&& (food.pos.y == judge->pos.y )) {
flag = false;
}
judge = judge->next;
}
}
gotoXY(food.pos.x, food.pos.y);
printf("@");
}
- 蛇移动函数(snakeMove)
蛇移动有两种种实现的方式:(1)清除蛇尾部,根据新的坐标新建蛇头,重新绘画蛇; (2)根据蛇头采用新的坐标,蛇身其他节点依次采用前一个节点的坐标; 该文章采用第二种实现方式
void snakerMove() {
int x = snakeHead->pos.x;
int y = snakeHead->pos.y;
//根据键盘事件确定蛇头新的坐标
switch (dirt) {
case right:
x += 2;
break;
case left:
x -= 2;
break;
case up:
y -= 1;
break;
case down:
y += 1;
break;
default:
break;
}
Snake *ptr = snakeHead;
int t_x, t_y;
//dirt 为space时,代表游戏刚开始,还没有键盘事件,蛇不动
while (dirt && ptr) {
t_x = ptr->pos.x;
t_y = ptr->pos.y;
ptr->pos.x = x;
ptr->pos.y = y;
x = t_x;
y = t_y;
ptr = ptr->next;
}
if (dirt) {
gotoPrint(snakeHead->pos.x, snakeHead->pos.y); //绘画新的蛇头
gotoDelete(x, y); //清除蛇尾
}
//该部分是增加游戏难度,达到一定分数以后提高蛇的行走速度
int count = score / 10;
if (count <= 10)
speed = 150;
else if (count > 10 && count <= 20)
speed = 100;
else if (count > 20 && count <= 40)
speed = 50;
else
speed = 20;
Sleep(speed);
}
- 键盘事件函数(keyPress)
int keyPress() {
int c;
while (1) {
//判断是否达到结束条件
if (Judge() == 0)
return 0;
//获取键盘事件
if (_kbhit()) {
_getch(); //方向键会输出两个值,去除第一个值,获取第二个值
c = dirt;
dirt = _getch();
//蛇不能倒退行驶; 蛇头初始位置在上面,开始不能向下运动
if (abs(c - dirt) == 8 || abs(c - dirt) == 2 || (c == space && dirt == down))
dirt = c;
}
snakerMove(); //蛇移动
snakerEating(); //吃食物
}
return 1;
}
- 吃食物(snakerEating)
蛇吃食物的判断条件是食物的坐标和蛇头的坐标相同。 蛇吃到食物后需要进行三项操作,一是随机新的食物,二是蛇身增加,三是更新得分
void snakerEating() {
//蛇头的位置同食物的位置相同
if (snakeHead->pos.x == food.pos.x
&& snakeHead->pos.y == food.pos.y) {
//创建新的食物
drawFood();
//蛇身加长,尾部增加新节点
Snake *ptr = snakeHead;
while (ptr->next) {
ptr = ptr->next;
}
Snake *nBody = (Snake *)malloc(sizeof(Snake));
nBody->next = NULL;
nBody->pos.x = ptr->pos.x;
nBody->pos.y = ptr->pos.y;
ptr->next = nBody;
//得分增加
score += 10;
gotoXY(77, 15);
printf("%d", score);
}
}
- 结束条件(Judge)
游戏结束条件有两种: 一是蛇撞击墙面,二是蛇撞击自己;两种情况都需要考虑
int Judge() {
//蛇撞墙
if (snakeHead->pos.x == 0 || snakeHead->pos.x == 58 ||
snakeHead->pos.y == 0 || snakeHead->pos.y == 26) {
gameOver();
}
//蛇撞自己
Snake *ptr = snakeHead->next;
while (ptr) {
if ((ptr->pos.x == snakeHead->pos.x) &&
(ptr->pos.y == snakeHead->pos.y)) {
gameOver();
return 0;
}
ptr = ptr->next;
}
return 1;
}
- 游戏结束页面(gameOver)
void gameOver() {
system("cls");
gotoXY(15, 10);
printf("/*************************************/");
gotoXY(20, 13);
printf("Game Over");
gotoXY(20, 15);
printf("Your Score is %d ", score);
gotoXY(15, 20);
printf("/*************************************/");
//释放链表
freeLink();
}
- 游戏结束,释放链表(freeLink)
void freeLink() {
Snake *ptr;
while (snakeHead) {
ptr = snakeHead;
snakeHead = snakeHead->next;
free(ptr);
}
}