一.技术要点
1.Win32 API函数
在贪吃蛇运行的过程中光标一直在闪动,影响游戏体验,我们需要隐藏光标以获得更好的游戏体验,并且我们还需要去定位光标的位置,这些都需要使用到Win32 API函数。
1.1什么是Win32 API函数
Win32 API,即Windows的32位应用程序编程接口(Application Programming Interface),是微软为软件开发者提供的一系列复杂的函数、消息、数据结构和控件的总称。它是创建在Windows 95、Windows NT以及之后版本操作系统上运行的应用程序的关键工具。它提供了大量的工具和接口,使得开发者能够创建出符合用户需求的桌面应用程序,用于访问Windows操作系统的功能。
1.2需要使用到的API函数
GetStdHandle : 用于获取相应控制台的句柄。
GetConsoleCursorInfo:用于获取控制台光标信息。
SetConsoleCursorInfo:用于设置控制台的光标信息。
GetAsyncKeyState:用于获取按键信息。
SetConsoleCursorInfo:用于设置控制台光标信息。
更多相关信息,请读者自行了解。
1.3使用示例
这是一个用于检测按键情况的示例
#include <stdio.h>
#include <windows.h>
int main()
{
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");
}
else if (KEY_PRESS(0x36))
{
printf("6\n");
}
else if (KEY_PRESS(0x37))
{
printf("7\n");
}
else if (KEY_PRESS(0x38))
{
printf("8\n");
}
else if (KEY_PRESS(0x39))
{
printf("9\n");
}
}
return 0;
}
这是设置光标信息的示例
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
更多的请自行了解
2.一些控制台程序
我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽
mode con cols=100 lines=30
这样可以设置为100列,30行
我们也可以用title来设置标题
title 贪吃蛇
设置标题为贪吃蛇
3.其他相关
1.COORD
COORD是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。
参数如下
使用示例
COORD pos = {10,20};
这就表示了x坐标为10,y坐标为20的位置
2.宽字符
我们知道C语言最开始适配的是英语,但随着c语言的发展,每个国家有自己的字符体系,为了是适配每个国家地区就出现了宽字符等概念。比如一个中文字符就是一个宽字符,占两个字节,可以简单理解为两个char类型的变量
但是使用宽字符并不能直接使用需要进行相关操作后才可以使用
2.1<locale.h>本地化
他是一个头文件,以适配当前地区所属的语言环境,它通常与setlocale一起使用
setlocale函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。setlocale的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和""(本地模式)。
我们需要设置为本地模式,故应该使用LC_ALL和""进行参数传递
2.2宽字符的使用
宽字符的字面量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引 号前⾯,表示宽字符,printf对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls 。
这是一个具体示例。
注意宽字符的使用不是char而是wchar_t这才表示的是宽字符。
这里是很简单粗略的介绍更多的请大家自行学习
3.CONSOLE_CURSOR_INFO
它的声明如下:
dwSize,由光标填充的字符单元格的百分⽐。此值介于1到100之间。光标外观会变化,范围从完 全填充单元格到单元底部的⽔平线条。
bVisible,游标的可⻅性。如果光标可⻅,则此成员为TRUE。
我们可以用它来修改光标信息从而使光标不可见,从而增加游戏体验,具体操作就是将bVisible成员变量置为false。
以上就是一些实现贪吃蛇的基础知识准备接下来我们就可以来实现贪吃蛇了。
二.思路简述
1.声明
在这个过程中我们需要维护贪吃蛇就是需要维护贪吃蛇的一些参数,所以我们可以声明贪吃蛇的一个结构体以便我们进行维护,同时蛇的每个身体可以看作一个节点去实现,因此我们可以使用链表作为贪吃蛇小游戏的数据结构。还有一些其它的声明直接看图。
2.封装SetPos函数
我们时刻要定位光标位置,所以封装一个定位函数可以帮助我们更好的实现
3.封装GameStart函数
游戏的开始包括的蛇的初始化,欢迎界面的打印,地图的绘制,贪吃蛇的绘制,因此我们还可以封装相应的函数去实现相应的功能。
4.GameRun函数
我们大致可以分为三个阶段,开始游戏,游戏运行,以及游戏的结束,分别对其进行实现。
上面就是开始游戏的函数封装。接下来就是最核心的游戏运行了。游戏运行伴随蛇的移动,检测是否撞墙,检测是否撞到自己,是否吃到食物等的功能,分别区实现就行了。不必赘述。
void GameRun(pSnake ps)
{
PrintHelpInfo();
do
{
SetPos(64,8);
printf("总分数:%d",ps->score);
SetPos(64,9);
printf("食物分数:%2d",ps->food_weight);
if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
{
ps->dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
{
ps->dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
{
ps->dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
{
ps->dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE))
{
Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
ps->status = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
if(ps->sleep_time > 80)
{
ps->sleep_time -= 30;
ps->food_weight += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
if (ps->sleep_time < 320)
{
ps->food_weight -= 2;
ps->sleep_time += 30;
}
}
SnakeMove(ps);
Sleep(ps->sleep_time);
} while (ps->status == OK);
}
4.GameEnd函数
最后就是游戏的善后的工作,比如节点的销毁等内容比较简单直接略过
void GameEnd(pSnake ps)
{
SetPos(64,20);
switch (ps->status)
{
case KILL_BY_WALL:
wprintf(L"您撞墙了,游戏结束");
break;
case KILL_BY_SELF:
wprintf(L"您撞到自己了,游戏结束");
break;
case END_NORMAL:
wprintf(L"正常游戏退出,游戏结束");
break;
}
pSnakeNode pcur = ps->psnake;
while (pcur)
{
pSnakeNode del = pcur;
pcur = pcur->next;
free(del);
}
free(ps->pfood);
ps->pfood = NULL;
ps->psnake = NULL;
}
三.源码实现
Snake.h文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <locale.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>
#include <time.h>
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define POS_X 24
#define POS_Y 5
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode,* pSnakeNode;
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
enum GAMESTATUS
{
OK,
KILL_BY_WALL,
KILL_BY_SELF,
END_NORMAL
};
typedef struct Snake
{
pSnakeNode psnake;
pSnakeNode pfood;
enum DIRECTION dir;
enum GAMESTATUES status;
int score;
int food_weight;
int sleep_time;
}Snake,* pSnake;
void SetPos(short x, short y);
void GameStart(pSnake ps);
void WelcomeToGame();
void CreateMap();
void InitSnake(pSnake ps);
void CreateFood(pSnake ps);
void GameRun(pSnake ps);
void SnakeMove(pSnake ps);
int NextIsFood(pSnake ps, pSnakeNode nextnode);
void EatFood(pSnake ps, pSnakeNode nextnode);
void KillByWall(pSnake ps);
void KillBySelf(pSnake ps);
void GameEnd(pSnake ps);
Snake.c文件
#include "Snake.h"
void SetPos(short x,short y)
{
COORD pos = {x,y};
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(houtput,pos);
}
void WelcomeToGame()
{
SetPos(38,12);
wprintf(L"欢迎来到贪吃蛇小游戏\n");
SetPos(39,14);
system("pause");
system("cls");
SetPos(25,12);
wprintf(L"用↑ ↓ ← →来控制蛇的移动,F3为加速,F4为减速\n");
SetPos(35,14);
wprintf(L"加速能获得更高的分数\n");
SetPos(40,25);
system("pause");
system("cls");
}
void CreateMap()
{
int i = 0;
for (i = 0;i < 29;i++)
{
wprintf(L"%lc",WALL);
}
SetPos(0,26);
for (i = 0;i < 29;i++)
{
wprintf(L"%lc", WALL);
}
for (i = 1;i <= 25;i++)
{
SetPos(0,i);
wprintf(L"%lc", WALL);
}
for (i = 1;i <= 25;i++)
{
SetPos(56,i);
wprintf(L"%lc", WALL);
}
}
void InitSnake(pSnake ps)
{
int i = 0;
pSnakeNode pcur = NULL;
for (i = 0;i < 5;i++)
{
pcur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (NULL == pcur)
{
perror("InitSnake malloc fail");
exit(1);
}
pcur->x = POS_X + 2 * i;
pcur->y = POS_Y;
pcur->next = ps->psnake;
ps->psnake = pcur;
}
while (pcur)
{
SetPos(pcur->x,pcur->y);
wprintf(L"%lc",BODY);
pcur = pcur->next;
}
ps->dir = RIGHT;
ps->status = OK;
ps->score = 0;
ps->sleep_time = 200;
ps->food_weight = 10;
}
void CreateFood(pSnake ps)
{
int x = 0;
int y = 0;
again:
do
{
y = rand() % 25 + 1;
x = rand() % 53 + 2;
} while (x % 2 != 0);
pSnakeNode pcur = ps->psnake;
while (pcur)
{
if (pcur->x == x && pcur->y == y)
{
goto again;
}
pcur = pcur->next;
}
pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (NULL == pfood)
{
perror("CreateFood malloc fail");
exit(1);
}
pfood->x = x;
pfood->y = y;
pfood->next = NULL;
ps->pfood = pfood;
SetPos(x,y);
wprintf(L"%lc",FOOD);
}
void GameStart(pSnake ps)
{
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info = {0};
GetConsoleCursorInfo(houtput,&cursor_info);
cursor_info.bVisible = false;
SetConsoleCursorInfo(houtput,&cursor_info);
WelcomeToGame();
CreateMap();
InitSnake(ps);
CreateFood(ps);
}
void PrintHelpInfo()
{
SetPos(64,12);
wprintf(L"%ls",L"不能穿墙,不能咬到自己");
SetPos(64,13);
wprintf(L"%ls", L"用↑ ↓ ← →来控制蛇的移动");
SetPos(64, 14);
wprintf(L"%ls", L"按F3加速,F4减速");
SetPos(64, 15);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}
#define KEY_PRESS(vk) GetAsyncKeyState(vk)&1
void Pause()
{
while (1)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
int NextIsFood(pSnake ps,pSnakeNode nextnode)
{
return (ps->pfood->x == nextnode->x && ps->pfood->y == nextnode->y);
}
void EatFood(pSnake ps,pSnakeNode nextnode)
{
nextnode->next = ps->psnake;
ps->psnake = nextnode;
free(ps->pfood);
ps->pfood = NULL;
pSnakeNode pcur = ps->psnake;
while (pcur)
{
SetPos(pcur->x, pcur->y);
wprintf(L"%lc",BODY);
pcur = pcur->next;
}
ps->score += ps->food_weight;
CreateFood(ps);
}
void NoFood(pSnake ps,pSnakeNode nextnode)
{
nextnode->next = ps->psnake;
ps->psnake = nextnode;
pSnakeNode pcur = ps->psnake;
while (pcur->next->next)
{
SetPos(pcur->x,pcur->y);
wprintf(L"%lc",BODY);
pcur = pcur->next;
}
SetPos(pcur->next->x,pcur->next->y);
printf(" ");
free(pcur->next);
pcur->next = NULL;
SetPos(pcur->x,pcur->y);
wprintf(L"%lc",BODY);
}
void KillByWall(pSnake ps)
{
if ((ps->psnake->x == 0)||( ps->psnake->x == 56)
||(ps->psnake->y == 0)||(ps->psnake->y == 26))
{
ps->status = KILL_BY_WALL;
}
}
void KillBySelf(pSnake ps)
{
pSnakeNode pcur = ps->psnake->next;
while (pcur)
{
if ((pcur->x == ps->psnake->x )&&(pcur->y == ps->psnake->y))
{
ps->status = KILL_BY_SELF;
break;
}
pcur = pcur->next;
}
}
void SnakeMove(pSnake ps)
{
pSnakeNode nextnode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (NULL == nextnode)
{
perror("SnakeMove malloc fail");
exit(1);
}
switch (ps->dir)
{
case UP:
nextnode->x = ps->psnake->x;
nextnode->y = ps->psnake->y - 1;
break;
case DOWN:
nextnode->x = ps->psnake->x;
nextnode->y = ps->psnake->y + 1;
break;
case LEFT:
nextnode->x = ps->psnake->x - 2;
nextnode->y = ps->psnake->y;
break;
case RIGHT:
nextnode->x = ps->psnake->x + 2;
nextnode->y = ps->psnake->y;
break;
}
if (NextIsFood(ps,nextnode))
{
EatFood(ps,nextnode);
}
else
{
NoFood(ps,nextnode);
}
KillByWall(ps);
KillBySelf(ps);
}
void GameRun(pSnake ps)
{
PrintHelpInfo();
do
{
SetPos(64,8);
printf("总分数:%d",ps->score);
SetPos(64,9);
printf("食物分数:%2d",ps->food_weight);
if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
{
ps->dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
{
ps->dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
{
ps->dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
{
ps->dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE))
{
Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
ps->status = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
if(ps->sleep_time > 80)
{
ps->sleep_time -= 30;
ps->food_weight += 2;
}
}
else if (KEY_PRESS(VK_F4))
{
if (ps->sleep_time < 320)
{
ps->food_weight -= 2;
ps->sleep_time += 30;
}
}
SnakeMove(ps);
Sleep(ps->sleep_time);
} while (ps->status == OK);
}
void GameEnd(pSnake ps)
{
SetPos(64,20);
switch (ps->status)
{
case KILL_BY_WALL:
wprintf(L"您撞墙了,游戏结束");
break;
case KILL_BY_SELF:
wprintf(L"您撞到自己了,游戏结束");
break;
case END_NORMAL:
wprintf(L"正常游戏退出,游戏结束");
break;
}
pSnakeNode pcur = ps->psnake;
while (pcur)
{
pSnakeNode del = pcur;
pcur = pcur->next;
free(del);
}
free(ps->pfood);
ps->pfood = NULL;
ps->psnake = NULL;
}
test.c文件
#include "Snake.h"
void test()
{
char ch = 0;
do
{
Snake snake = { 0 };
GameStart(&snake);
GameRun(&snake);
GameEnd(&snake);
SetPos(64,22);
wprintf(L"再来一句请输入Y,否则N:");
ch = getchar();
while (getchar() != '\n');
} while ('Y' == ch || 'y' == ch);
}
int main()
{
srand((unsigned int)time(NULL));
setlocale(LC_ALL,"");
test();
SetPos(0,27);
return 0;
}
四.运行示例
贪吃蛇
五.心得
虽然写得不详细,但是对你有那么一点微小的作用,自己去实现一下会大大的促进你的能力,希望大家都能找到一丝进步的身影。快速使用方式,直接看第三大点就行。