贪吃蛇是我做的第一个小程序,这里分享一下大一上学期写的或者网上找的几个版本的控制台贪吃蛇程序。
贪吃蛇编写思路
游戏规则:通过上下左右按键控制贪吃蛇吃食物,每吃一个食物蛇就会变成并且获得分数,撞到地图或者自己的身体则gameover,胜利的规则可以是到达一定的长度或者分数,也可以是其他条件。
一个简单的思路:把整个地图当作一个二维字符数组,并且把蛇和食物都”画“在地图上,边界、蛇和食物都是用特殊字符表示,这样游戏的一帧就是打印整个地图。
伪代码:
初始化地图、蛇、食物
死循环
暂停0.n秒
如果有按键则接收按键改变蛇头的方向
移动蛇(更新地图)
判断是否吃到食物
如果吃到食物蛇变长 分数增加 重新生成食物(更新地图)
判断是否撞到墙或者自己的身体
如有有游戏结束
判断是否胜利(规则自己定)
重新显示地图
运行结果
-
snake1
-
snake2
-
snake3
4 .snake4(使用了双缓冲)
-
snake(加了音乐的)
可以发现从刚开始很闪烁到后来几乎不闪烁,体验也变好了。
不同之处
- snake1程序使用的双重for循环打印的地图
- snake2程序使用了单层for循环打印的地图
- snake3和snake程序没有使用循环,而是做了局部更新
- snake4使用了循环打印地图并且使用了双缓冲
解析
双重for循环打印地图的每一个点,一次用到了几百个printf,
单层for循环打印地图的每一行(把printf一个字符变成了printf一个字符串),一次用到了几十个printf,
局部更新,一次只用到了几个printf,
双缓冲则从根本上解决了闪烁的问题,只刷新一次。
设置光标位置
局部更新则是使用了控制台的函数SetConsoleCursorPosition
设置光标位置,这样我们就不需要用system("cls")
刷新整个地图了。
// 在指定位置打印图形
// 参数:x坐标 y坐标 需要打印的字符串
// 无返回值
void MyPrintf(int iPosX, int iPosY, char *szSymbol)
{
// 设置光标位置 SetConsoleCursorPosition()
// 第一个参数为控制台的句柄 可由 GetStdHandle()得到 STD_OUTPUT_HANDLE为输出
// 第二个参数为坐标结构体
COORD pos; // 创建结构体pos
pos.X = iPosX; // 结构体成员赋值
pos.Y = iPosY;
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 得到窗口句柄
SetConsoleCursorPosition(hOutput, pos); // 设置光标位置
printf(szSymbol);
}
双缓冲
snake2和snake4对比
snake2直接输出在显示缓冲区里:
snake4:
- 将要输出的数据写在缓冲区一(写的过程中显示的是缓冲区二的内容)
- 显示缓冲区一的内容
- 将要输出的数据写在缓冲区二(写的过程中显示的是缓冲区一的内容)
- 显示缓冲区二的内容 ,回到第1步
创建控制台屏幕缓冲区
snake4:
// 创建可写的控制台缓冲区
HANDLE hOutBuf1 = CreateConsoleScreenBuffer(GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
HANDLE hOutBuf2 = CreateConsoleScreenBuffer(GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
其中CreateConsoleScreenBuffer:
HANDLE WINAPI CreateConsoleScreenBuffer(
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ const SECURITY_ATTRIBUTES *lpSecurityAttributes,
_In_ DWORD dwFlags,
_Reserved_ LPVOID lpScreenBufferData
);
参数:
- dwDesiredAccess — 对控制台屏幕缓冲区的访问。
- dwShareMode
- 0 — 表示不能共享缓冲区
- FILE_SHARE_READ — 可以在控制台屏幕缓冲区上执行其他打开操作以进行读取访问
- FILE_SHARE_WRITE — 可以在控制台屏幕缓冲区上执行其他打开操作以进行写访问
- lpSecurityAttributes — 确定子进程是否可以继承返回的句柄
- dwFlags — 要创建的控制台屏幕缓冲区的类型。唯一受支持的屏幕缓冲区类型为CONSOLE_TEXTMODE_BUFFER。
- lpScreenBufferData — 保留,应该为NULL。
使用缓冲区画地图
// 画地图
void drawMap(HANDLE hOutBuf, HANDLE hOutput)
{
COORD coord = { 0,0 };
DWORD bytes = 0;
static _Bool flag = true;
if (flag)
{
for (int i = 0; i <= 27; ++i)
{
coord.Y = i;
WriteConsoleOutputCharacterA(hOutBuf, g_map[i], strlen(g_map[i]), coord, &bytes);
}
SetConsoleActiveScreenBuffer(hOutBuf); // 设置新的缓冲区为活动显示缓冲
flag = false;
}
else
{
for (int i = 0; i <= 27; ++i)
{
coord.Y = i;
WriteConsoleOutputCharacterA(hOutput, g_map[i], strlen(g_map[i]), coord, &bytes);
}
SetConsoleActiveScreenBuffer(hOutput); // 设置新的缓冲区为活动显示缓冲
flag = true;
}
}
其中WriteConsoleOutputCharacterA:
BOOL WINAPI WriteConsoleOutputCharacter(
_In_ HANDLE hConsoleOutput,
_In_ LPCTSTR lpCharacter,
_In_ DWORD nLength,
_In_ COORD dwWriteCoord,
_Out_ LPDWORD lpNumberOfCharsWritten
);
功能:从指定位置开始,将多个字符复制到控制台屏幕缓冲区的连续单元中。
参数:
- hConsoleOutput — 控制台屏幕缓冲区的句柄。该句柄必须具有GENERIC_WRITE访问权限。
- lpCharacter — 要写入控制台屏幕缓冲区的字符。
- nLength — 要写入的字符数。
- dwWriteCoord — 指定控制台屏幕缓冲区中要写入字符的第一个单元格的字符坐标。
- lpNumberOfCharsWritten — 指向变量的指针,该变量接收实际写入的字符数。
返回值:
- 如果函数成功,则返回值为非零。
- 如果函数失败,则返回值为零。
SetConsoleActiveScreenBuffer:
BOOL WINAPI SetConsoleActiveScreenBuffer(
_In_ HANDLE hConsoleOutput
);
功能:将指定的屏幕缓冲区设置为当前显示的控制台屏幕缓冲区。
参数:
- hConsoleOutput — 控制台屏幕缓冲区的句柄。
返回值:
- 如果函数成功,则返回值为非零。
- 如果函数失败,则返回值为零。
代码链接
控制台帮助文档 https://docs.microsoft.com/en-us/windows/console/console-reference
程序百度云链接:https://pan.baidu.com/s/1oiOAsdI4Zi40Le-DznzAyg
提取码:ei4w