在开始阶段我们还是来借鉴网页版的贪吃蛇游戏贪吃蛇,看看其中有哪些功能,想想用C语言该如何实现,当这个游戏还没完成的时候,我就在思考,该如何用键盘控制,如果不按键盘该怎么办?别着急,往下看。
游戏思路
首先游戏有一个边界,我们可以用一个函数来打印边界,蛇就在边界之中移动,有一个函数实现蛇的移动,一个函数用来控制蛇的方向。每移动一次就要实时打印。还有一块食物,如果被蛇吃了,蛇会增加身体长度,食物会重新随机刷新在边界之中,蛇碰到边界或者吃到自己的身体就会死亡。
边界
定义两个宏,L是边界的长,W是边界的宽
#define L 60
#define W 28
void show_map()
{
for (int i = 0; i <= W; i++)
{
for (int j = 0; j <= L; j++)
{
//如果在边界的四个角上,那么打印+号
if ((i == 0 && j == 0) || (i == 0 && j == L)||
(i == W && j == 0) || (i == W && j == L))
{
printf("+");
}
//如果在两个长边上,打印-
else if (i == 0 || i == W )
{
printf("-");
}
//如果在两个短边上,打印|
else if (j == 0 || j == L)
{
printf("|");
}
//如果不是上面三种情况的话,就打印空格
else
{
printf(" ");
}
}
//每打印一行要换行
printf("\n");
}
}
这是打印情况:
蛇的结构体
typedef struct _body //坐标x,y的结构体
{
int x;
int y;
}BODY;
typedef struct _snake
{
int size; //蛇的长度
BODY arr[L * W]; //蛇的所有坐标的数组,大小为L*W
BODY food; //食物位置
//蛇的移动方向//
int dx;
int dy;
COORD coord; //等等再说
BODY tail; //等等再说
//分数
int score;
}SNAKE;
如果有人不知道typedef的用法可以去C语言中文了解一下。简单来说就是为结构体的类型定义了新名称,比如将struct _body定义了新名称BODY。
我们可以将蛇的每一个部分都看成是两个坐标(可以用数组实现,但是比较麻烦),比如头的坐标,蛇身某一节的坐标,这样只要知道蛇的大小然后在相应的坐标位置打印就好了。
当我们控制蛇移动时,蛇该往哪移动?我们可以定义int dx,int dy,比如蛇往右移动时,因为在控制台中往下是x正轴,往右是y正轴,所以dx=0,dy=1。我们遍历蛇的每节,每一节的x都+=dx,y都+=dy就可以实现蛇的移动。
初始化蛇的参数
void unit_snake(SNAKE* snake)
{
snake->size = 2; //初始是2节
//蛇头,在地图的中心位置
snake->arr[0].x = W / 2;
snake->arr[0].y = L / 2;
//蛇尾
snake->arr[1].x = W / 2;
snake->arr[1].y = L / 2 - 1 ;
//初始化食物
unit_food(&(snake->food));
//移动,默认往右移动
snake->dx = 0;
snake->dy = 1;
//分数初始化为0
snake->score = 0;
//尾巴等等有说
snake->tail = snake->arr[1];
}
void unit_food(BODY* food)
{
food->x = rand() % (W-1)+1;
food->y = rand() % (L - 1) + 1;
//因为地图的坐标范围是:1<=x<=27 0<=y<=59,不能让食物生成在边界上
}
隐藏控制台光标
因为每次打印的时候,会有光标一直在闪烁,但是贪吃蛇游戏里不需要这个,只需要在主函数第一行(打印前)用上这个函数就行了。
void hide_cur()
{
//隐藏控制台光标
CONSOLE_CURSOR_INFO cci;
cci.dwSize = sizeof(cci);
cci.bVisible = FALSE;
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);
}
打印蛇和食物
如果蛇、食物、边界在一起的话,必须要将他们都放在同一个数组。这样非常慢,因为每次都需要将所有的信息打印一遍。
但是windows.h中定义了一个结构体它可以控制光标的位置,也就是说,我们只需要先打印一个边界,然后在边界中(也就是空格填满的区域)打印我们的蛇和食物就好了。
这是定义中的结构体,注意有一点其中的X坐标是向右为x轴正方向的,Y坐标是向下为y轴正方向,和我们之前的x,y坐标不同。
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD;
不需要我们来定义,我们只需要创建它的变量然后使用就可以了,我们在蛇的结构体中有创建它。这是具体使用:
coor.X = 20;
coor.Y = 10;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),coord);
//这代表着最开始光标在控制台的左上角,然后将光标向右移动20格,再向下移动10格,在这个位置开始打印
接下来实现打印我们的蛇和食物
void show_snake_food(SNAKE* snake)
{
//设置光标位置,打印蛇
for (int i = 0; i < snake->size; i++)
{
//这就是区别,为什么X赋值y,因为两个的参考坐标系不同
snake->coord.X = snake->arr[i].y;
snake->coord.Y = snake->arr[i].x;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
if (i == 0)
{
printf("@"); //蛇头的字符
}
else
{
printf("&"); //蛇身的字符
}
}
//设置光标位置,打印食物*
snake->coord.X = snake->food.y;
snake->coord.Y = snake->food.x;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
printf("*");
//尾巴变为空格,每移动一次-等等讲
snake->coord.X = snake->tail.y;
snake->coord.Y = snake->tail.x;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
printf(" ");
}
这是打印效果,因为其中有代码会将尾巴变成空格,所以没打印尾巴。而这个变为空格的代码是为了,蛇每一次移动,都要将原来已经打印了尾巴的位置,变为空白。
如果没有将尾巴去除的代码那么就会变成下面这样:
这显然不是我们想要的。
蛇的移动
这里我们就看看蛇是怎么移动的
void move_snake(SNAKE* snake)
{
snake->tail.x = snake->arr[snake->size - 1].x;
snake->tail.y = snake->arr[snake->size - 1].y;
//移动时,记录尾巴的坐标,等等打印的时候要将这个位置打印空格
for (int i = snake->size - 1; i > 0; i--) //身体的坐标移动
{
snake->arr[i] = snake->arr[i - 1];//从后往前赋值,这样就不会覆盖数据
}
//蛇头的坐标移动,蛇头单独赋值
snake->arr[0].x += snake->dx;
snake->arr[0].y += snake->dy;
}
蛇的控制
这就是贪吃蛇最核心的部分了,如果我们去控制蛇,用什么接收?用getchar()?当然不行,因为每按一次要回车一下并且它会使得程序停止。
有一个_khbit()函数,它可以检查当控制台打开时是否有按键按下,如果有就返回一个非0的值,如果没有就返回0,就算你没有按下任何按键,它也会在执行完返回一个0,它不会使程序停下来。
_khbit()和_getch()的头文件都是 <conio.h>。
char key;
while (_kbhit()) //如果按下了按键,_getch()会获取输入的字符
{
key = _getch();//并且不会在控制台中显示这个字符,返回值是输入的字符
}
void control_snake(SNAKE* snake)
{
char key=0;
while (_kbhit())
{
key = _getch();
}
switch (key)
{
//向左
case 'A':
case 'a':
snake->dx = 0;
snake->dy = -1;
break;
//向右
case 'D':
case 'd':
snake->dx = 0;
snake->dy = 1;
break;
//向上
case 'W':
case 'w':
snake->dx = -1;
snake->dy = 0;
break;
//向下
case 'S':
case 's':
snake->dx = 1;
snake->dy = 0;
break;
}
//大小写都可以移动
}
蛇吃到食物
void eat_food(SNAKE* snake)
{
if (snake->arr[0].x == snake->food.x &&
snake->arr[0].y == snake->food.y) //当头的坐标和食物的坐标相同的时候
{
unit_food(&(snake->food)); //将食物再次生成
snake->size++; //蛇的长度+1
snake->score += 10; //得分加10
}
}
在这里我们并不需要对增加的蛇尾研究,因为吃到食物size会自增1,在蛇移动时是从后向前赋值的,所以这增加的蛇尾会得到之前蛇尾的坐标也就是值。
蛇吃到自己以及游戏结束
int eat_body(SNAKE* snake)
{
for (int i = 1; i < snake->size; i++)
{
if (snake->arr[0].x == snake->arr[i].x &&
snake->arr[0].y == snake->arr[i].y) //遍历蛇身比较蛇头
{
game_over(snake);
return 1;
}
}
return 0;
}
void game_over(SNAKE* snake)
{
snake->coord.X = 21;
snake->coord.Y = 32;
//在一个合适的位置打印
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
printf("得分:%d ,", snake->score);
printf("游戏结束 !\n");
}
游戏函数
void game(SNAKE* snake)
{
while (snake->arr[0].x > 0 && snake->arr[0].x < W
&& snake->arr[0].y>0 && snake->arr[0].y < L) //是否超出边界
{
show_snake_food(snake);
control_snake(snake);
eat_food(snake);
if (eat_body(snake))
goto end;
Sleep(70); //睡眠0.07s
move_snake(snake);
}
game_over(snake);
end:
;
}
主函数和函数实现和头文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "test.h"
int main()
{
srand((time_t)time(NULL)); //初始化随机数种子
hide_cur(); //隐藏光标
SNAKE* snake =(SNAKE*) malloc(sizeof(SNAKE)); //创建蛇结构体
unit_snake(snake); //初始化蛇和食物
show_map(); //打印边界
game(snake);
Sleep(3000);
free(snake);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "test.h"
void show_map()
{
for (int i = 0; i <= W; i++)
{
for (int j = 0; j <= L; j++)
{
if ((i == 0 && j == 0) || (i == 0 && j == L)||
(i == W && j == 0) || (i == W && j == L))
{
printf("+");
}
else if (i == 0 || i == W )
{
printf("-");
}
else if (j == 0 || j == L)
{
printf("|");
}
else
{
printf(" ");
}
}
printf("\n");
}
}
void unit_snake(SNAKE* snake)
{
snake->size = 2;
//蛇头
snake->arr[0].x = W / 2;
snake->arr[0].y = L / 2;
//蛇尾
snake->arr[1].x = W / 2;
snake->arr[1].y = L / 2 - 1 ;
//食物
unit_food(&(snake->food));
//移动
snake->dx = 0;
snake->dy = 1;
//分数
snake->score = 0;
//尾巴
snake->tail = snake->arr[1];
}
void unit_food(BODY* food)
{
food->x = rand() % (W-1)+1;
food->y = rand() % (L - 1) + 1;
}
void hide_cur()
{
//隐藏控制台光标
CONSOLE_CURSOR_INFO cci;
cci.dwSize = sizeof(cci);
cci.bVisible = FALSE;
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cci);
}
void show_snake_food(SNAKE* snake)
{
//设置光标位置,打印蛇
for (int i = 0; i < snake->size; i++)
{
snake->coord.X = snake->arr[i].y;
snake->coord.Y = snake->arr[i].x;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
if (i == 0)
{
printf("@");
}
else
{
printf("&");
}
}
//设置光标位置,打印食物
snake->coord.X = snake->food.y;
snake->coord.Y = snake->food.x;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
printf("*");
//尾巴变为空格,没移动一次
snake->coord.X = snake->tail.y;
snake->coord.Y = snake->tail.x;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
printf(" ");
}
void game(SNAKE* snake)
{
while (snake->arr[0].x > 0 && snake->arr[0].x < W
&& snake->arr[0].y>0 && snake->arr[0].y < L)
{
show_snake_food(snake);
control_snake(snake);
eat_food(snake);
if (eat_body(snake))
goto end;
Sleep(70);
move_snake(snake);
}
game_over(snake);
end:
;
}
void move_snake(SNAKE* snake)
{
snake->tail.x = snake->arr[snake->size - 1].x;
snake->tail.y = snake->arr[snake->size - 1].y;
for (int i = snake->size - 1; i > 0; i--) //尾巴的坐标移动
{
snake->arr[i] = snake->arr[i - 1];
}
//蛇头的坐标移动
snake->arr[0].x += snake->dx;
snake->arr[0].y += snake->dy;
}
void control_snake(SNAKE* snake)
{
char key=0;
while (_kbhit())
{
key = _getch();
}
switch (key)
{
case 'A':
case 'a':
snake->dx = 0;
snake->dy = -1;
break;
case 'D':
case 'd':
snake->dx = 0;
snake->dy = 1;
break;
case 'W':
case 'w':
snake->dx = -1;
snake->dy = 0;
break;
case 'S':
case 's':
snake->dx = 1;
snake->dy = 0;
break;
}
}
void eat_food(SNAKE* snake)
{
if (snake->arr[0].x == snake->food.x &&
snake->arr[0].y == snake->food.y)
{
unit_food(&(snake->food));
snake->size++;
snake->score += 10;
}
}
int eat_body(SNAKE* snake)
{
for (int i = 1; i < snake->size; i++)
{
if (snake->arr[0].x == snake->arr[i].x &&
snake->arr[0].y == snake->arr[i].y)
{
game_over(snake);
return 1;
}
}
return 0;
}
void game_over(SNAKE* snake)
{
snake->coord.X = 21;
snake->coord.Y = 32;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), snake->coord);
printf("得分:%d ,", snake->score);
printf("游戏结束 !\n");
}
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h> //检测键盘输入
#define L 60
#define W 28
typedef struct _body
{
int x;
int y;
}BODY;
typedef struct _snake
{
int size; //蛇长度
BODY arr[L * W];
BODY food; //食物位置
//蛇的移动方向//
int dx;
int dy;
COORD coord;
BODY tail; //蛇尾巴
//分数
int score;
}SNAKE;
void show_map(); //打印边界
void unit_snake(SNAKE* snake); //初始化蛇
void unit_food(BODY *boay); //初始化食物位置
void hide_cur();//隐藏控制台光标
void show_snake_food(SNAKE* snake); //打印蛇和食物
void game(SNAKE* snake); //开始游戏
void move_snake(SNAKE* snake); //移动蛇
void control_snake(SNAKE* snake);
void eat_food(SNAKE* snake);
int eat_body(SNAKE* snake);
void game_over(SNAKE* snake);