贪吃蛇解决闪烁的方法

本文分享大一上编写或网上找的几个版本控制台贪吃蛇程序。介绍编写思路,展示运行结果,对比不同程序差异。重点解析双缓冲技术,它从根本上解决闪烁问题,还介绍创建控制台屏幕缓冲区及用缓冲区画地图的相关函数,最后给出代码链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

贪吃蛇是我做的第一个小程序,这里分享一下大一上学期写的或者网上找的几个版本的控制台贪吃蛇程序。

贪吃蛇编写思路

游戏规则:通过上下左右按键控制贪吃蛇吃食物,每吃一个食物蛇就会变成并且获得分数,撞到地图或者自己的身体则gameover,胜利的规则可以是到达一定的长度或者分数,也可以是其他条件。

一个简单的思路:把整个地图当作一个二维字符数组,并且把蛇和食物都”画“在地图上,边界、蛇和食物都是用特殊字符表示,这样游戏的一帧就是打印整个地图。
伪代码

初始化地图、蛇、食物
死循环
	暂停0.n秒
	如果有按键则接收按键改变蛇头的方向
	移动蛇(更新地图)
	判断是否吃到食物
		如果吃到食物蛇变长 分数增加 重新生成食物(更新地图)
	判断是否撞到墙或者自己的身体
		如有有游戏结束
	判断是否胜利(规则自己定)
	重新显示地图

运行结果

  1. snake1
    在这里插入图片描述

  2. snake2
    在这里插入图片描述

  3. snake3
    在这里插入图片描述
    4 .snake4(使用了双缓冲)
    在这里插入图片描述

  4. 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. 将要输出的数据写在缓冲区一(写的过程中显示的是缓冲区二的内容)
  2. 显示缓冲区一的内容
  3. 将要输出的数据写在缓冲区二(写的过程中显示的是缓冲区一的内容)
  4. 显示缓冲区二的内容 ,回到第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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值