游凡/贪吃蛇https://gitee.com/you-fan-a/greedy-snake
#这里我们用控制台程序来完成贪吃蛇这个游戏。
首先,打开编译器,随便写点东西运行,出现运行窗口,右键运行窗口的上边窗。
点击属性,将默认终端应用程序改为控制台主机。
#需要用到的编程知识
1.cmd命令
//需要包含头文件 #include<Windows.h>
system("mode con cols=100 lines=30");
//设置窗口的大小 30行 100列
system("title 贪吃蛇");
//设置窗口的名称 贪吃蛇
效果如下:
2.控制台的坐标
COORD是windows API自带的一种结构体,说白了就是可以直接用的结构体。
typedef struct _COORD {
SHORT X; //光标的X坐标
SHORT Y; //光标的Y坐标
} COORD, *PCOORD;
COORD pos={12,13}; //给光标赋值(决定光标位置)
3.Windows API函数
3.1GetStdHandle 函数
该函数用来获取标准设备(标准输入、标准输出和标准错误)的一个句柄,有了这个句柄就可以操控设备了。(可以理解为获取了设备的操作权限)
//HANDLE是数据类型
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
3.2CONSOLE_CURSOR_INFO 结构体
这个结构体包含控制台光标的信息
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize; //dwSize表示光标的宽度,宽度值介于1~100之间,100时是个高长方形
BOOL bVisible; //bVisible表示光标的可见性,当他的值时是TURE时光标可见
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
cursorinfo.bVisible = false; //隐藏光标
//cursorinfo是结构体名称
3.3GetConsoleCursorInfo 函数
函数原型:
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
获取控制台光标信息
HANDLE hOutput = NULL;
hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取标准输出的句柄(⽤来标识不同设备的数值)
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo); //获取控制台光标信息
3.4SetConsoleCursorInfo 函数
函数原型:
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
设置控制台光标信息
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//STD_OUTPUT_HANDLE为输出设备句柄
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
3.5SetConsoleCursorPosition 函数
函数原型:
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
设置控制台光标位置
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
3.6GetAsyncKeyState 函数
函数原型:
SHORT GetAsyncKeyState(
int vKey
);
//在上一次函数调用后,函数返回的16位数据,如果最高位是1,则说明按键是按下的状态,否则最高位为1.
//如果最低位是1,则说明按键被按过,否则为0.
获取键盘状态。虚拟键码:虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn
将该函数的返回值与1,判断是否按过
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
使用案例
while (1)
{
if (KEY_PRESS(0x30))
{
printf("0\n");
}
else if (KEY_PRESS(0x31))
{
printf("1\n");
}
else if (KEY_PRESS(0x32))
{
printf("2\n");
}
else if (KEY_PRESS(0x33))
{
printf("3\n");
}
else if (KEY_PRESS(0x34))
{
printf("4\n");
}
else if (KEY_PRESS(0x35))
{
printf("5\n");
}
}
#项目思路
一、设置游戏窗口
将窗口设置成合适大小,将窗口的名称改为贪吃蛇,并隐藏控制台光标。
void ConsoleInit()
{
system("mode con cols=100 lines=30");//将窗口大小改为30行,100列
system("title 贪吃蛇");//名称改为贪吃蛇
HANDLE handleput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO consolecursorinfo;
GetConsoleCursorInfo(handleput, &consolecursorinfo);
consolecursorinfo.bVisible = false;//将光标设置为不可见
SetConsoleCursorInfo(handleput, &consolecursorinfo);
}
二、创建蛇及其相关结构体
typedef struct Snake//创建蛇结构体,用链表链接蛇身
{
wchar_t body;
int x;
int y;
struct Snake* next;
}Snake;
typedef enum state//创建状态枚举,用于判断游戏是否结束
{
OK,
END_BODY,
END_WALL,
END_ESC,
SPACE
}state;
typedef enum key//游戏按键
{
up,
down,
right,
left
}key;
typedef enum food//贪吃蛇的食物,不同的食物又不同分数
{
score1,//★
score2,//▲
score3,//▼
uneat,
eated
}food;
typedef struct pos//坐标
{
int x;
int y;
}pos;
typedef struct SnakeGame//游戏功能的集合
{
Snake *snake;
enum food food;
enum state state;
enum key key;
int score;//游戏总分数
int speed;//游戏速度
int extrascore;//额外分
pos foodpos;//食物坐标
}Game;
三、对游戏进行初始化
void GameInit(Game* const game)
{
game->state = OK;//游戏状态
game->key = right;//贪吃蛇最开始移动方向
game->food = uneat;//食物状态
game->score = 0;//分数
game->speed = 0;//速度
game->extrascore = 0;//额外分数
}
四、展示最开始的提示
#define cols 60
#define lines 20
//设置坐标
void setpos(int x,int y)//写一个设置控制台坐标的函数,方便以后设置坐标
{
HANDLE handleput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { x , y };
SetConsoleCursorPosition(handleput, pos);
}
//展示最初提示
void ShowInitialScreen()
{
setpos(40, 15);
printf("欢迎来到贪吃蛇游戏");
setpos(40, 24);
system("pause");
system("cls");
setpos(25, 15);
printf("使用↑ ↓ ← → 操控蛇,去吃食物(★、▲、▼),并避免撞到墙(□)");
setpos(40, 24);
system("pause");
system("cls");
setpos(0, 0);
for (int i = 0; i < (cols/2); i++)
{
printf("□");
}
setpos(0, 20);
for (int i = 0; i < (cols/2); i++)
{
printf("□");
}
for (int i = 1; i <= lines-1; i++)
{
setpos(0, i);
printf("□");
}
for (int i = 1; i <= lines-1; i++)
{
setpos(58, i);
printf("□");
}
setpos(65, 8);
printf("F1加速 F2减速");
setpos(70, 12);
printf("当前分数:");
setpos(70, 13);
printf("当前速度:");
setpos(65, 15);
printf("★5分 ▲7分 ▼10分");
setpos(65, 16);
printf("每一点速度都会为食物加2分");
}
五、创建蛇身
5.1创建蛇头
//创建蛇的头
Snake* CreateSnake()
{
Snake* ret = (Snake*)malloc(sizeof(Snake));
if (ret != NULL)
{
ret->body = L'◆';//这是宽字符
ret->next = NULL;
return ret;
}
else
{
perror("malloc");
return NULL;
}
}
5.2创建蛇身
//创建最开始的蛇
void CreatInitialSnake(Game* game)
{
Snake* ret = NULL;
for (int i = 0; i < 5; i++)//从后往前建造蛇身
{
Snake* snake = (Snake*)malloc(sizeof(Snake));
if (snake != NULL)
{
if (i == 4)
{
snake->body = L'◆';
}
else
{
snake->body = L'●';
}
snake->x = 30 + i*2;//设置蛇身的坐标
snake->y = 9;
snake->next = ret;
ret = snake;
game->snake = snake;
}
else
{
perror("malloc");
}
}
}
六、判断当前状态
void JudgeState(Game* const game)
{
//检测是否撞到墙
if (game->snake->x == 0 || game->snake->x == cols - 2 || game->snake->y == 0 || game->snake->y == lines)
{
game->state = END_WALL;
setpos(20, 10);
printf("你撞到障碍物失败");
return;
}
//检测是否撞到自己
Snake* ret = game->snake->next;
while (ret != NULL)
{
if (ret->x == game->snake->x && ret->y == game->snake->y)
{
game->state = END_BODY;
setpos(20, 10);
printf("你撞到自己失败");
return;
}
ret = ret->next;
}
//检测是否按Esc退出
if (game->state == END_ESC)
{
setpos(20, 10);
printf("已退出");
}
//检测是否暂停
if (game->state==SPACE)
{
setpos(0,24);
system("pause");
setpos(0, 24);
printf(" ");
game->state = OK;
}
}
七、食物
7.1判断食物是否被吃
void JudgeFoodEated(Game* const game)
{
if (game->snake->x == game->foodpos.x && game->snake->y == game->foodpos.y)
{
//根据食物种类的不同,所加的分也不同
if (game->food == score1)
{
game->score += (5+game->extrascore);
}
else if (game->food == score2)
{
game->score += (7 + game->extrascore);
}
else if (game->food == score3)
{
game->score += (10 + game->extrascore);
}
game->food = eated;//食物被吃后,将食物的状态改eated
}
}
7.2生成食物
void Food(Game* const game)
{
if (game->food == uneat)
{
int cl, lin;
again: //goto语句
cl = rand() % (cols-5) + 1;//随机生成食物的位置
lin = rand() % (lines - 2) + 1;
if ((cl % 2) != 0)
{
cl = cl + 1;
}
for (Snake* ret = game->snake; ret != NULL; ret = ret->next)
{
if (cl == ret->x && lin == ret->y)//防止食物生成在蛇身里
{
goto again;
}
}
game->foodpos.x = cl;
game->foodpos.y = lin;
int score = rand() % 2 + 1;//随机生成三种不同的食物
if (score == 1)
{
game->food = score1;
setpos(cl, lin);
printf("★");
}
else if (score == 2)
{
game->food = score2;
setpos(cl, lin);
printf("▲");
}
else if (score == 3)
{
game->food = score3;
setpos(cl, lin);
printf("▼");
}
}
else
{
JudgeFoodEated(game);//判断食物是否被吃
}
}
八、检测虚拟按键
#define ASYNC(VK) ((GetAsyncKeyState(VK)&1)? 1 : 0 )
//控制按钮
if (ASYNC(VK_UP) && game->key != down)
{
game->key = up;
}
else if (ASYNC(VK_DOWN) && game->key != up)
{
game->key = down;
}
else if (ASYNC(VK_RIGHT) && game->key != left)
{
game->key = right;
}
else if (ASYNC(VK_LEFT) && game->key != right)
{
game->key = left;
}
else if (ASYNC(VK_SPACE))
{
game->state = SPACE;
}
else if (ASYNC(VK_ESCAPE))
{
game->state = END_ESC;
}
else if (ASYNC(0x70))
{
time = time - 20;
game->extrascore += 2;
game->speed += 1;
}
else if (ASYNC(0x71))
{
time = time + 20;
game->extrascore -= 2;
game->speed -= 1;
}
//按测到按键按下,对蛇头进行更改
Snake* con = CreateSnake();
if (con != NULL)
{
if (game->key == up)
{
con->body = L'◆';
con->x = game->snake->x;
con->y = game->snake->y - 1;
game->snake->body = L'●';
con->next = game->snake;
game->snake = con;
}
if (game->key == down)
{
con->body = L'◆';
con->x = game->snake->x;
con->y = game->snake->y + 1;
game->snake->body = L'●';
con->next = game->snake;
game->snake = con;
}
if (game->key == right)
{
con->body = L'◆';
con->x = game->snake->x + 2;
con->y = game->snake->y;
game->snake->body = L'●';
con->next = game->snake;
game->snake = con;
}
if (game->key == left)
{
con->body = L'◆';
con->x = game->snake->x - 2;
con->y = game->snake->y;
game->snake->body = L'●';
con->next = game->snake;
game->snake = con;
}
}
else
{
perror("malloc");
}
九、打印游戏进行的画面
if (game->food != eated)//判断食物是否被被吃
{
//食物没有被吃,释放掉蛇尾(蛇移动时蛇尾要消失)
for (Snake* ret = game->snake; ret->next != NULL; ret = ret->next)
{
if (ret->next->next == NULL)
{
setpos(ret->next->x, ret->next->y);
wprintf(L"%lc", L' ');//这是宽字符打印
free(ret->next);
ret->next = NULL;
break;
}
}
}
else
{
game->food = uneat;//食物被吃,则更改食物状态,以便重新生成食物
}
//打印蛇身
if (game->snake != NULL && game->snake->next != NULL)
{
setpos(game->snake->x, game->snake->y);
wprintf(L"%lc", game->snake->body);
setpos(game->snake->next->x, game->snake->next->y);
wprintf(L"%lc", game->snake->next->body);
Sleep(time);
}
else
{
perror("NULL");
}
//打印份数和速度
setpos(79, 12);
printf("%d", game->score);
setpos(79, 13);
printf("%d", game->speed);
Food(game);//生成食物
#将八和九的代码放在一起(需添加格外的代码)
void SetGame(Game* game)
{
CreatInitialSnake(game);
for (Snake* ret = game->snake; ret != NULL; ret = ret->next)
{
setpos(ret->x, ret->y);
wprintf(L"%lc", ret->body);
}
int time = 100;
do
{
//控制按钮
if (ASYNC(VK_UP) && game->key != down)
{
game->key = up;
}
else if (ASYNC(VK_DOWN) && game->key != up)
{
game->key = down;
}
else if (ASYNC(VK_RIGHT) && game->key != left)
{
game->key = right;
}
else if (ASYNC(VK_LEFT) && game->key != right)
{
game->key = left;
}
else if (ASYNC(VK_SPACE))
{
game->state = SPACE;
}
else if (ASYNC(VK_ESCAPE))
{
game->state = END_ESC;
}
else if (ASYNC(0x70))
{
time = time - 20;
game->extrascore += 2;
game->speed += 1;
}
else if (ASYNC(0x71))
{
time = time + 20;
game->extrascore -= 2;
game->speed -= 1;
}
Snake* con = CreateSnake();
if (con != NULL)
{
if (game->key == up)
{
con->body = L'◆';
con->x = game->snake->x;
con->y = game->snake->y - 1;
game->snake->body = L'●';
con->next = game->snake;
game->snake = con;
}
if (game->key == down)
{
con->body = L'◆';
con->x = game->snake->x;
con->y = game->snake->y + 1;
game->snake->body = L'●';
con->next = game->snake;
game->snake = con;
}
if (game->key == right)
{
con->body = L'◆';
con->x = game->snake->x + 2;
con->y = game->snake->y;
game->snake->body = L'●';
con->next = game->snake;
game->snake = con;
}
if (game->key == left)
{
con->body = L'◆';
con->x = game->snake->x - 2;
con->y = game->snake->y;
game->snake->body = L'●';
con->next = game->snake;
game->snake = con;
}
}
else
{
perror("malloc");
}
JudgeState(game);
//打印游戏进行的画面
if (game->food != eated)
{
for (Snake* ret = game->snake; ret->next != NULL; ret = ret->next)
{
if (ret->next->next == NULL)
{
setpos(ret->next->x, ret->next->y);
wprintf(L"%lc", L' ');
free(ret->next);
ret->next = NULL;
break;
}
}
}
else
{
game->food = uneat;
}
if (game->snake != NULL && game->snake->next != NULL)
{
setpos(game->snake->x, game->snake->y);
wprintf(L"%lc", game->snake->body);
setpos(game->snake->next->x, game->snake->next->y);
wprintf(L"%lc", game->snake->next->body);
Sleep(time);
}
else
{
perror("NULL");
}
setpos(79, 12);
printf("%d", game->score);
setpos(79, 13);
printf("%d", game->speed);
Food(game);
} while (game->state == OK);
}
十、重新开始
void test()
{
char ch=-1;
do
{
system("cls");//清屏
Game game;
GameInit(&game);
ConsoleInit();
ShowInitialScreen();
SetGame(&game);
setpos(10, 11);
printf("按Enter重新开始, 输入其他结束游戏");
ch= getchar();
} while (ch == '\n');
setpos(0, 26);
system("exit");//结束退出
}
最后调用test就可以了
int main()
{
srand((unsigned int)time(NULL));//随机数种子
setlocale(LC_ALL, "");//本地化,可以不加,但是如果是用我的代码,必须加上
test();
return 0;
}