今天写了人生第一条蛇——贪吃蛇的V1.0版本。
来分享下吧。
下面先贴代码。
#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>
#include<conio.h>
/*=========================================================================================================*/
#define LEN 30
#define WID 25
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
/*=========================================================================================================*/
int snake_direction = LEFT;
int snake_length = 3;
bool alive = true;
bool eatup = false;
/*=========================================================================================================*/
//光标相关
void Set_Cursor_To(int x, int y);
//蛇相关
void direction_snake();
void flag_snake();
void draw_snake();
void eraser_snake();
//食物相关
void product_food();
//环境相关
void draw_boundary();
//控制相关
void Game_Over();
void initialize();
/*=========================================================================================================*/
//光标相关
HANDLE handle_console = GetStdHandle(STD_OUTPUT_HANDLE);
//设置光标位置
void Set_Cursor_To(int x, int y)
{
COORD position = { x, y };
SetConsoleCursorPosition(handle_console, position);
}
//蛇相关
int snake[LEN][WID] = { 0 }; //全地图标识数组
//检测移动方向
void direction_snake()
{
if (kbhit())
{
int ch = getch();
switch (getch())
{
case UP: snake_direction = UP; break;
case DOWN: snake_direction = DOWN; break;
case LEFT: snake_direction = LEFT; break;
case RIGHT: snake_direction = RIGHT;break;
default: break;
}
}
eraser_snake();
flag_snake();
draw_snake();
}
//标记蛇
void flag_snake()
{
int Snake_ix, Snake_iy;
for (int x = 0; x < LEN; x++)
for (int y = 0; y < WID; y++)
{
if (snake[x][y] == 1)//记录蛇头Snake_ix/iy
{
switch (snake_direction)
{
case UP:Snake_ix = x; Snake_iy = y - 1; break;
case DOWN:Snake_ix = x; Snake_iy = y + 1; break;
case LEFT:Snake_ix = x-1; Snake_iy = y; break;
case RIGHT:Snake_ix = x+1; Snake_iy = y; break;
default: break;
}
if (Snake_ix == 0 || Snake_iy == 0 || Snake_ix == LEN || Snake_iy == WID)//检查移动后头坐标是否满足规则
{
alive = false;
}
if (snake[Snake_ix][Snake_iy] > 0)//检查移动后头位置是否满足规则
{
alive = false;
}
if (snake[Snake_ix][Snake_iy] == -1)
{
snake_length++;
eatup = true;
}
}
if (snake[x][y] == snake_length)//必要时清除蛇尾
{
snake[x][y] = 0;
}
if (snake[x][y] > 0)//对蛇身进行编号
{
snake[x][y] += 1;
}
}
snake[Snake_ix][Snake_iy] = 1;
}
//画蛇
void draw_snake()
{
for (int i = 0; i < LEN;i++)
for (int j = 0; j < WID;j++)
{
if (snake[i][j]>0)
{
Set_Cursor_To(2 * i, j);
printf("■");
}
}
}
//除尾
void eraser_snake()
{
for (int i = 0; i < LEN;i++)
for (int j = 0; j < WID; j++)
{
if (snake[i][j] == snake_length)
{
Set_Cursor_To(2 * i, j);
printf(" ");
}
}
}
//食物相关
//生产食物
void product_food()
{
int food_ix, food_iy;
food_ix = rand() % LEN;
food_iy = rand() % WID;
while (snake[food_ix][food_iy] != 0)
{
food_ix = rand() % LEN;
food_iy = rand() % WID;
}
snake[food_ix][food_iy] = -1;
Set_Cursor_To(2 * food_ix, food_iy);
printf("★");
eatup = false;
}
//环境相关
//边界
void draw_boundary()
{
for (int i = 0; i < LEN; i++)//上下边界
{
Set_Cursor_To(2 * i, 0);
printf("--");
Set_Cursor_To(2 * i, WID - 1);
printf("--");
snake[i][0] = -2;
snake[i][WID - 1] = -2;
}
for (int i = 0; i < WID; i++)
{
Set_Cursor_To(0, i);
printf("|");
Set_Cursor_To(2*LEN - 1, i);
printf("|");
snake[0][i] = -2;
snake[LEN-1][i] = -2;
}
}
//控制相关
//判定游戏结束
void Game_Over()
{
Set_Cursor_To(25, 12);
printf("~~傻屌,你死了~~");
Set_Cursor_To(21, 13);
printf("Press any key to replay.");
system("pause > nul");
}
//初始化
void initialize()
{
for (int x = 0; x < LEN; x++)
for (int y = 0; y < WID; y++)
{
snake[x][y] = 0;
}
system("title Bit_Percy_贪吃蛇V1.0");
system("mode con:cols=62 lines= 27");
draw_boundary();
for (int i = 0; i < snake_length; i++) //初始化蛇
{
snake[i + 12][12] = i + 1;
}
draw_snake();
product_food();
alive = true;
}
/*=========================================================================================================*/
int main()
{
while (1)
{
system("cls");
initialize();
while (1)
{
if (alive == false)
{
Game_Over();
break;
}
else
{
if (eatup == true)
{
product_food();
}
direction_snake();
Sleep(100);
}
}
}
return 0;
}
/*=========================================================================================================*/
接下来分块说了~
首先呢,写这玩意最好先列个提纲。。要不然会死的= =。。
上面程序的注释当提纲已经很清楚了。下面来依次解释下吧。
第一部分:预编译部分
#define LEN 30
#define WID 25
#define UP 72
#define DOWN 80
#define LEFT 75
#define RIGHT 77
WID表示场地 行数。
UP是键盘“上”的键值。
DOWN是键盘“下”的键值。
LEFT是键盘“左”的键值。
RIGHT是键盘“右”的键值。
第二部分:声明部分
int snake_direction = LEFT;
int snake_length = 3;
bool alive = true;
bool eatup = false;
/*=========================================================================================================*/
//光标相关
void Set_Cursor_To(int x, int y);
//蛇相关
void direction_snake();
void flag_snake();
void draw_snake();
void eraser_snake();
//食物相关
void product_food();
//环境相关
void draw_boundary();
//控制相关
void Game_Over();
void initialize();
初始蛇的长度设置为3。
初始生命设置为true。
初始食物是否吃完设置为false。
之后是几个函数的声明。
第三部分:首先介绍光标相关的函数:
//光标相关
HANDLE handle_console = GetStdHandle(STD_OUTPUT_HANDLE);
//设置光标位置
void Set_Cursor_To(int x, int y)
{
COORD position = { x, y };
SetConsoleCursorPosition(handle_console, position);
}
一个句柄是指使用的一个唯一的整数值,即一个四字节长的数值,来标识应用程序中的不同对象和同类对象中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是一个指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不用在I/O文件中,它是毫无用处的。 句柄是windows用来标志应用程序中建立的或是使用的唯一整数,windows使用了大量的句柄来标志很多对象。(摘自百度百科)
总之,句柄虽然不是一个指针,但我们可以在用途方面初步地理解为一个指针,就相当于win下资源的身份证,无论窗口,画板,画笔,都有一个唯一的句柄来访问。比如你开了2个qq窗口,系统只用句柄来区别他们。程序也可以用相关api通过句柄访问。之所以说它不是一个指针,是因为指针是指向内存的,而句柄本质上是一个无符号数字。
好了,我们现在知道什么是句柄了。这里HANDLE声明的就是一个句柄handle_console。
这个句柄用来表示什么东西呢?我们使用GetStdHandle(STD_OUTPUT_HANDLE)来获得标准输出设备(一般是显示器这里就是控制台了)的句柄。
COORD是一个windows API中定义的一个结构体。
就相当于:
typedef struct _COORD { // coord.
SHORT X; // horizontal coordinate
SHORT Y; // vertical coordinate
} COORD;
我们用它来表示 坐标。
通过光标相关的函数,我们可以把光标发送到地图的任意一个角落,来执行输出操作。
第四部分:蛇相关
int snake[LEN][WID] = { 0 }; //全地图标识数组
首先定义全地图标识数组。
4.1:检测移动方向
void direction_snake()
{
if (kbhit())
{
int ch = getch();
switch (getch())
{
case UP: snake_direction = UP; break;
case DOWN: snake_direction = DOWN; break;
case LEFT: snake_direction = LEFT; break;
case RIGHT: snake_direction = RIGHT; break;
default: break;
}
}
eraser_snake();
flag_snake();
draw_snake();
}
我们让ch来接受这个传入的字符。如果是上下左右时,将蛇的前进方向设置为UP、DOWN、LEFT、RIGHT。
之后来执行的3个函数是为了让蛇动起来。
这三个函数在接下来会说。
4.2:标记蛇
void flag_snake()
{
int Snake_ix, Snake_iy;
for (int x = 0; x < LEN; x++)
for (int y = 0; y < WID; y++)
{
if (snake[x][y] == 1)//记录蛇头Snake_ix/iy
{
switch (snake_direction)
{
case UP:Snake_ix = x; Snake_iy = y - 1; break;
case DOWN:Snake_ix = x; Snake_iy = y + 1; break;
case LEFT:Snake_ix = x - 1; Snake_iy = y; break;
case RIGHT:Snake_ix = x + 1; Snake_iy = y; break;
default: break;
}
if (Snake_ix == 0 || Snake_iy == 0 || Snake_ix == LEN || Snake_iy == WID)//检查移动后头坐标是否满足规则
{
alive = false;
}
if (snake[Snake_ix][Snake_iy] > 0)//检查移动后头位置是否满足规则
{
alive = false;
}
if (snake[Snake_ix][Snake_iy] == -1)
{
snake_length++;
eatup = true;
}
}
if (snake[x][y] == snake_length)//必要时清除蛇尾
{
snake[x][y] = 0;
}
if (snake[x][y] > 0)//对蛇身进行编号
{
snake[x][y] += 1;
}
}
snake[Snake_ix][Snake_iy] = 1;
}
然后根据方向对蛇头进行坐标上的移动。
在检验移动后的坐标位置是否不可到达(到达边界活着与蛇身相撞)。如果是则死亡。
还需要检验是否迟到了食物,如果吃到,蛇应该变长。
必要情况(没有吃到食物时)下还应当清除蛇尾。
之后再对整条蛇重新编号。
这就是整个标记蛇的过程。
4.3:画蛇
void draw_snake()
{
for (int i = 0; i < LEN; i++)
for (int j = 0; j < WID; j++)
{
if (snake[i][j]>0)
{
Set_Cursor_To(2 * i, j);
printf("■");
}
}
}
由于标记时所有有蛇的坐标点全部都是正值,有食物的坐标点讲置为-1,边界将置为-2,所以画蛇只需要将所有值大于0的坐标点涂黑了~
4.4:除尾
void eraser_snake()
{
for (int i = 0; i < LEN; i++)
for (int j = 0; j < WID; j++)
{
if (snake[i][j] == snake_length)
{
Set_Cursor_To(2 * i, j);
printf(" ");
}
}
}
由于计划每次都重新画蛇,所以我们每次都除去尾巴即可。
除去尾巴的操作很简单啦。。不说了
第五部分:食物相关
//生产食物
void product_food()
{
int food_ix, food_iy;
food_ix = rand() % LEN;
food_iy = rand() % WID;
while (snake[food_ix][food_iy] != 0)
{
food_ix = rand() % LEN;
food_iy = rand() % WID;
}
snake[food_ix][food_iy] = -1;
Set_Cursor_To(2 * food_ix, food_iy);
printf("★");
eatup = false;
}
我们的食物用星星来表示。
rand()函数用来产生一个随机数。我们要让食物在地图内的可用点上,也就是说必须是地图 上的,而且是标记为0的点 才可能有食物。
为了满足这个条件,我们使用如上方法。
第六部分:环境相关
//边界
void draw_boundary()
{
for (int i = 0; i < LEN; i++)//上下边界
{
Set_Cursor_To(2 * i, 0);
printf("--");
Set_Cursor_To(2 * i, WID - 1);
printf("--");
snake[i][0] = -2;
snake[i][WID - 1] = -2;
}
for (int i = 0; i < WID; i++)
{
Set_Cursor_To(0, i);
printf("|");
Set_Cursor_To(2 * LEN - 1, i);
printf("|");
snake[0][i] = -2;
snake[LEN - 1][i] = -2;
}
}
第七部分:控制相关
//判定游戏结束
void Game_Over()
{
Set_Cursor_To(25, 12);
printf("~~傻屌,你死了~~");
Set_Cursor_To(21, 13);
printf("Press any key to replay.");
system("pause > nul");
}
//初始化
void initialize()
{
for (int x = 0; x < LEN; x++)
for (int y = 0; y < WID; y++)
{
snake[x][y] = 0;
}
system("title Bit_Percy_贪吃蛇V1.0");
system("mode con:cols=62 lines= 27");
draw_boundary();
for (int i = 0; i < snake_length; i++) //初始化蛇
{
snake[i + 12][12] = i + 1;
}
draw_snake();
product_food();
alive = true;
}
system("title xxxxxxxx");用于替换控制台的名称。
system("mode con:cols = 62 lines = 27");用于控制控制台的大小。
第八部分:主函数
int main()
{
while (1)
{
system("cls");
initialize();
while (1)
{
if (alive == false)
{
Game_Over();
break;
}
else
{
if (eatup == true)
{
product_food();
}
direction_snake();
Sleep(100);
}
}
}
return 0;
}
sleep(100);是让程序休眠0.1s 。修改这里的值可以调节蛇的速度。
system("cls");是清屏命令。