【C语言】贪吃蛇
文章目录
前言
相信大家都知道贪吃蛇这款游戏,它是一款经典的手机游戏。通过控制蛇头方向吃食物,使得蛇变长,从而获得积分,既简单又耐玩。通过上下左右键控制蛇的方向,寻找吃的东西,每吃一口就能得到一定的积分,而且蛇的身子会越吃越长,身子越长玩的难度就越大,不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴,分数越高蛇的速度越快。现在让我们通过C语言来实现贪吃蛇的代码吧。
模块描述
蛇的初始化
蛇的初始化实际就是二维数组的初始化,该二维数组存储两个值,里面包含该蛇身体的坐标信息,它出现的初始位置是横纵坐标的中间位置。
蛇的移动
蛇的移动是通改变二维数组坐标位置来实现的,例如当蛇向右前进一个单位,则将尾每一个身体位置的坐标更改,同时改变蛇头、蛇身以及蛇尾的方向。这样整体来看来蛇就前进了一个单位。
蛇的增长
当蛇吃了正常食物后,蛇的长度会增加。
蛇的死亡
当蛇撞上障碍物、自身或者时,蛇会死亡,游戏结束。
食物的产生
食物出现的位置产生都是随机的,这些因素由通过随机函数获取的随机数决定。食物的位置不能出现在障碍物和边界上。
控制键盘输入
通过获取键盘输入的W/w(上)、 S/s(下)、 A/a(左)、 D/d(右)来改变蛇模块中移动方向,从而影响蛇的移动方向。
效果展示
完整代码
snake.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>
#include <Windows.h>
#define WIDE 60 //宽度
#define HIGH 20 //高度
struct BODY {
int X;
int Y;
}; //一个身体的坐标
struct SNAKE {
struct BODY body[WIDE * HIGH]; //蛇身体的上限,铺满整个空间。
int size; //蛇的大小
}snake; //全局蛇变量
struct FOOD {
int X;
int Y;
}food; //全局食物变量
int score = 0; //分数
int dx = 0; //用户按下 adws任意一个按键所得到的 坐标值。
int dy = 0;
int lastX = 0; //蛇尾的坐标
int lastY = 0;
int count = 0; //吃食物的次数
int sleepSecond = 300;
//声明函数
void initSnake(void);
void initFood(void);
void initUI(void);
void playGame(void);
void initWall(void);
void showScore(void);
main.h
#include "snake.h" // 引入自定义头文件
int main()
{
//去除光标
CONSOLE_CURSOR_INFO cci;
cci.dwSize = sizeof(cci);
cci.bVisible = FALSE; //0
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);
srand((unsigned int)time(NULL)); //播种随机数种子
initSnake(); //初始化蛇
initFood(); //初始化食物
initWall(); //画墙
initUI(); //画蛇和食物
playGame(); //启动游戏
showScore(); //打印分数
system("pause");
return 0;
}
//初始化蛇
void initSnake(void)
{
snake.size = 2;
snake.body[0].X = WIDE / 2;
snake.body[0].Y = HIGH / 2;
snake.body[1].X = WIDE / 2 - 1;
snake.body[1].Y = HIGH / 2;
return;
}
//初始化食物
void initFood(void)
{
food.X = rand() % WIDE;
food.Y = rand() % HIGH;
return;
}
//画墙
void initWall(void)
{
for (size_t i = 0; i <= HIGH; i++) //多行
{
for (size_t j = 0; j <= WIDE; j++)
{
if (j == WIDE)
{
putchar('|');
}
else if (i == HIGH)
{
putchar('_');
}
else
{
putchar(' ');
}
}
printf("\n");
}
}
//初始化界面控件
void initUI(void)
{
COORD coord = {0}; //光标移动位置
//画蛇
for (size_t i = 0; i < snake.size; i++)
{
coord.X = snake.body[i].X;
coord.Y = snake.body[i].Y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
if (i == 0)
putchar('@');
else
putchar('*');
}
//去除蛇尾
coord.X = lastX;
coord.Y = lastY;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
putchar(' ');
//画食物
coord.X = food.X;
coord.Y = food.Y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
putchar('#');
}
//启动游戏
void playGame(void)
{
char key = 'd';
//判断蛇撞墙
while (snake.body[0].X >= 0 && snake.body[0].X < WIDE
&& snake.body[0].Y >= 0 && snake.body[0].Y < HIGH)
{
//更新蛇
initUI();
//接收用户按键 adws
if (_kbhit()) {
key = _getch();
}
switch (key)
{
case 'a': dx = -1; dy = 0; break;
case 'd': dx = 1; dy = 0; break;
case 'w': dx = 0; dy = -1; break;
case 's': dx = 0; dy = 1; break;
default: break;
}
//蛇头撞身体 : 蛇头撞任意一节身体
for (size_t i = 1; i < snake.size; i++)
{
if (snake.body[0].X == snake.body[i].X
&& snake.body[0].Y == snake.body[i].Y)
{
return; //游戏结束
}
}
//蛇头撞食物
if (snake.body[0].X == food.X && snake.body[0].Y == food.Y)
{
initFood(); //食物消失
snake.size++; //身体增长
score += 10; //加分
if (count <= 8)
{
sleepSecond -= 25; //加速
}
count++;
}
//储存蛇尾坐标
lastX = snake.body[snake.size - 1].X;
lastY = snake.body[snake.size - 1].Y;
//蛇移动,前一节身体给后一节身体赋值
for (size_t i = snake.size - 1; i > 0; i--)
{
snake.body[i].X = snake.body[i - 1].X;
snake.body[i].Y = snake.body[i - 1].Y;
}
snake.body[0].X += dx;
snake.body[0].Y += dy;
Sleep(sleepSecond);
}
}
//打印分数
void showScore(void)
{
//将光标位置移动至不干扰游戏的任意位置
COORD coord;
coord.X = 0;
coord.Y = HIGH + 2;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
printf("Game Over!!!\n");
printf("成绩为:%d\n\n\n", score);
}
代码拆分
定义蛇对象
蛇有身体和长度,通过结构体来储存相关信息。
#define WIDE 60 //宽度
#define HIGH 20 //高度
struct BODY {
int X;
int Y;
}; //一个身体的坐标
struct SNAKE {
struct BODY body[WIDE * HIGH]; //蛇身体的上限,铺满整个空间。body[0]蛇头
int size; //蛇的大小
}snake; //全局蛇变量
定义食物对象
struct FOOD {
int X;
int Y;
}food; //全局食物变量
int score = 0; //分数定义
初始化蛇对象
默认将蛇头位于空间中心,蛇头朝右。
void initSnake(void)
{
snake.size = 2;
snake.body[0].X = WIDE / 2; //初始化蛇头
snake.body[0].Y = HIGH / 2;
snake.body[1].X = WIDE / 2 - 1; //初始化一节蛇身
snake.body[1].Y = HIGH / 2;
return;
}
食物的初始化
食物在空间内的任意位置,用随机数生成。
#include <stdlib.h>
#include <time.h>
srand((unsigned int)time(NULL)); //播种随机数种子
void initFood(void)
{
food.X = rand() % WIDE;
food.Y = rand() % HIGH;
return;
}
修改控制台光标位置
默认光标在起始位置,画蛇要现将光标移动到蛇头和蛇身的位置在将蛇头和蛇身打印到屏幕这里的蛇头用@蛇身用*,食物用#来表示。
需要函数:
SetConsoleCursorPosition函数:
包含头文件 : include<conio.h> <Windows.h>
typedef struct_COORD{
SHORT X; //x坐标
SHORT Y; //y坐标
}COORD; //COORD是Win对哦晚上API中定义的一种结构,表示一个字符在控制台屏幕上的坐标。
COORD coord;
coord.X=10;
coord.Y=20;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
画蛇和食物
COORD coord={0}; //光标移动位置
//画蛇
for (size_t i = 0; i < snake.size; i++)
{
coord.X = snake.body[i].X;
coord.Y = snake.body[i].Y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
if (i == 0)
putchar('@');
else
putchar('*');
}
//画食物
coord.X = food.X;
coord.Y = food.Y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
putchar('#');
蛇移动控制基础知识
按 adws 来控制蛇移动方向,影响蛇头位置的变化:
W:(0,-1) S:(0,+1) A:(-1,0) D:(+1,0)
要将输入的 adws 不显示屏幕上且光标一直闪在需要函数:
getch() 无回显接收用户输入一个字符。
用法: char key = getch();
kbhit() 以非阻塞方式,检查当前是否有键盘输入。
用法:if(kbhit()){…}
游戏控制逻辑实现,接收键盘输入
判断蛇撞墙,撞上自己,吃到食物
//判断蛇撞墙
while (snakebody[0].X >= 0 && snake.body[0].X < WIDE
&& snake.body[0].Y >= 0 && snake.body[0].Y < HIGH)
{
for (size_t i = 1; i < snake.size; i++)
{
//蛇头撞身体 : 蛇头撞任意一节身体
if (snake.body[0].X == snake.body[i].X
&& snake.body[0].Y == snake.body[i].Y)
{
return; //游戏结束
}
}
//蛇头撞食物
if (snake.body[0].X == food.X && snake.body[0].Y == food.Y)
{
initFood(); //食物消失更新
snake.size++; //身体增长
score += 10; //加分
}
}
蛇的移动控制
通过adws控制蛇移动,蛇头单独移动,蛇的每一个身体由前一节身体赋值。不按按键默认项右跑。改变后的坐标要重画一遍蛇,这样蛇才可以运动起来。更新的蛇都向前一个单位,要将之前的蛇尾位置的*消掉的用‘ ’代替。每次更新的间隔时间就是蛇运动的速度。用Sleep()函数处理。
//更新蛇
initUI();
int dx = 0; //用户按下 adws任意一个按键所得到的 坐标值。
int dy = 0;
char key = 'd'; //初值
//接收用户按键 adws
if (_kbhit()) {
key = _getch();
}
switch (key)
{
case 'a': dx = -1; dy = 0; break;
case 'd': dx = 1; dy = 0; break;
case 'w': dx = 0; dy = -1; break;
case 's': dx = 0; dy = 1; break;
default: break;
}
//蛇移动,前一节身体给后一节身体赋值
for (size_t i = snake.size - 1; i > 0; i--)
{
snake.body[i].X = snake.body[i - 1].X;
snake.body[i].Y = snake.body[i - 1].Y;
}
snake.body[0].X += dx; //蛇头改变
snake.body[0].Y += dy;
//储存蛇尾坐标
lastX = snake.body[snake.size - 1].X;
lastY = snake.body[snake.size - 1].Y;
//去除蛇尾
coord.X = lastX;
coord.Y = lastY;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
putchar(' ');
画墙
将边缘用 ‘|’ 和 ‘_’ 来占据空间其余的空间用 ‘ ’占据。
//画墙
void initWall(void)
{
for (size_t i = 0; i <= HIGH; i++) //多行
{
for (size_t j = 0; j <= WIDE; j++) //一行中的多列
{
if (j == WIDE)
{
putchar('|');
}
else if (i == HIGH)
{
putchar('_');
}
else
{
putchar(' ');
}
}
printf("\n");
}
}
设置光标不可见
需要函数:
SetConsoleCursorInfo函数
CONSOLE_CURSOR_INFO 结构体,描述终端光标信息。包含两成员变量。
typedef struct _CONSOLE_CURSOR_INFO{
DWORD dwSize //大小
BOOL bVisible //是否可见
} CONSOLE_CURSOR_INFO ;
//去除光标
CONSOLE_CURSOR_INFO cci;
cci.dwSize = sizeof(cci);
cci.bVisible = FALSE; //0 不可见
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci); //设置光标不可见生效,只需在main函数中调用一次
加速和显示成绩
//显示分数
void showScore(void)
{
//将光标位置移动至不干扰游戏的任意位置
COORD coord;
coord.X = 0;
coord.Y = HIGH + 2;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
printf("Game Over!!!\n");
printf("成绩为:%d\n\n\n", score);
}
吃到8个食物后速度达到最大值
int sleepSecond = 300;
int count = 0; //吃食物的次数
if (count <= 8)
{
sleepSecond -= 25; //加速
}
count++;
Sleep(sleepSecond);
总结
以上贪吃蛇的基本规则算是完成了。每一个项目,在实现之前都要进行分析设计,项目整体要实现哪些功能。将这些功能划分成不同的模块,如果模块较大还可以在内部划分成更小的功能模块。这样逐个实现每个模块,条理清晰。在实现各个模块后,需要将模块整合,使各个功能协调有序的进行。在进行模块划分和模块整合时,可以使用流程图来表示模块之间的联系与运行流程。
标签:body,int,void,coord,C语言,贪吃蛇,snake,size From: https://blog.csdn.net/2301_80035097/article/details/137285083