C语言—贪吃蛇(链表)超详解

news2024/11/16 6:25:21

目录

游戏背景

游戏展示效果

需要实现的功能

贪吃蛇地图绘制:

蛇吃食物的功能:

蛇的移动控制:

蛇撞墙死亡:

蛇撞自身死亡:

计算得分:

蛇身加速、减速:

暂停游戏:

技术要点

win32 API

控制台程序

控制台坐标 COORD 

 成员:

​编辑

什么是句柄?

示例用法:

GetConsoleCursorInfo 

CONSOLE_CURSOR_INFO 结构体:

SetConsoleCursorInfo 

示例用法:

SetConsoleCursorPosition 

示例用法:

getAsyncKeyState

示例用法:

步骤一:获取控制台窗口句柄

步骤二:获取当前窗口样式

步骤三:移除允许调整大小的样式

步骤四:应用新的窗口样式

贪吃蛇

宽字符

setlocale 函数

宽字符的打印

地图

流程图

main.c(解析全在注释中)

Game_start

Game_run

Game_end

代码

main.c

text.c

text.h


游戏背景

        贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。 在编程语⾔的教学中,我们以贪吃蛇为例,从设计到代码实现来提升学⽣的编程能⼒和逻辑能⼒。

注意:本篇只适合有C语言基础的朋友


游戏展示效果


需要实现的功能

  1. 贪吃蛇地图绘制

    • 需求:创建一个二维的游戏界面,显示由方格组成的地图,其中蛇和食物的位置清晰可见。
    • 实现:可以使用图形编程库(如Python的pygame、JavaScript的canvas等)来绘制固定大小的网格地图,用不同颜色或图案表示空地、蛇身、食物等元素。
  2. 蛇吃食物的功能

    • 需求:当蛇头移动到与食物相同位置时,蛇吃掉食物,长度增加一节,并重新在地图上随机生成新的食物。
    • 实现:在每帧更新时检查蛇头坐标是否与食物坐标重合,若重合则触发进食逻辑,延长蛇身、更新蛇的总长度,并使用随机数生成算法在地图有效区域内产生新的食物位置。
  3. 蛇的移动控制

    • 需求:通过上、下、左、右方向键(或相应触控/手势)改变蛇的移动方向,使其在下一帧按照新方向前进一格。
    • 实现:监听用户输入事件,识别方向键按下状态,更新蛇的内部“目标方向”变量。在游戏循环中,根据该变量计算蛇头下一位置,并相应更新整个蛇身的位置。
  4. 蛇撞墙死亡

    • 需求:当蛇头移动到地图边界之外时,游戏结束,显示死亡画面或提示。
    • 实现:在蛇移动逻辑中加入边界检查,如果蛇头的新位置超出地图范围,则触发游戏结束逻辑,停止游戏循环,展示死亡画面及分数。
  5. 蛇撞自身死亡

    • 需求:当蛇头移动到其身体其他部分所在的位置时,游戏结束,显示死亡画面或提示。
    • 实现:在蛇移动后更新所有身体位置之前,检查蛇头的新位置是否与已有的身体部分重叠,若重叠则触发游戏结束逻辑,同上。
  6. 计算得分

    • 需求:根据蛇吃到的食物数量累计得分,分数实时显示在游戏中。
    • 实现:每当蛇吃到食物时,将分数变量加一,并在游戏界面上显示当前分数。可以设计简单的计分规则,如每吃一个食物得一分,或者根据食物类型或连续进食次数设定不同的得分规则。
  7. 蛇身加速、减速

    • 需求:提供机制使蛇在一定条件下(如吃到特殊食物、达到特定分数等)加速或减速,改变蛇移动的速度(即每帧前进的格子数)。
    • 实现:设置蛇的移动速度变量,初始值为基础速度。当满足加速条件时,提高该速度值;满足减速条件时,降低该速度值。游戏循环中蛇的移动距离应基于当前速度值进行计算。
  8. 暂停游戏

    • 需求:提供暂停按钮或快捷键,使得玩家能在游戏进行中暂时停止游戏进程,恢复时能从暂停点继续游戏。
    • 实现:添加暂停/继续功能的用户输入响应,如点击按钮或按下指定键。在游戏循环中检测暂停状态,暂停时停止更新游戏状态和渲染,继续时恢复游戏循环。

技术要点

C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等

如果不知道什么是链表的就看:

数据结构:单链表-CSDN博客文章浏览阅读1.6k次,点赞36次,收藏14次。链表是一种基本的数据结构,它用于存储一系列元素(节点),每个节点不仅包含数据元素,还包含一个指向下一个节点的指针。在链表中,数据并非连续地存储在内存中,而是通过每个节点的指针链接起来形成一个逻辑上的线性序列通过前面我们学习的顺序表我们现在延伸一个链表我们会发现顺序表的一些缺点。https://blog.csdn.net/2302_78381559/article/details/137829309?spm=1001.2014.3001.5502

其他的知识都是c语言基础的,我们重点讲解一下会用到的几个win32api的函数

win32 API

Win32 APIApplication Programming Interface)是Microsoft Windows操作系统为软件开发人员提供的一个标准编程接口集。它专为32位(及后来的64位兼容)版本的Windows设计,允许程序员编写能在这些操作系统上运行的应用程序,并能够充分利用Windows的各种系统功能。Win32 API本质上是一组预先定义好的函数、结构、常量、消息和宏,它们封装了与操作系统交互的复杂细节。

这是文档链接:
Win32 API 编程参考 - Win32 应用 |Microsoft学习icon-default.png?t=N7T8https://learn.microsoft.com/en-us/windows/win32/api/

控制台程序

改变我们的控制台的大小

mode con cols=200 lines:200

mode: 这是一个在命令提示符中内置的命令,用于修改各种系统设置和配置。在此处,它用于更改控制台的模式。

con: 这代表“console”(控制台)设备。它表明您希望修改的是与当前控制台窗口相关的设置。

cols=200: cols 短语表示“columns”(列)。cols=200 设置控制台窗口的宽度,即将其水平方向上的字符列数设定为200个。这意味着您可以在一行内显示最多200个字符。

lines=200: lines 表示“lines”(行)。lines=200 设置控制台窗口的高度,即将其垂直方向上的字符行数设定为200行。这意味着您可以看到总共200行的文本输出。

参考链接mode | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows-server/administration/windows-commands/mode

也可以通过命令设置控制台窗⼝的名字:
 

title 贪吃蛇

title中文意思就是标题

参考链接title | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows-server/administration/windows-commands/title

可以通过这个代码去试一下效果

#include<stdlib.h>
int main() {
	//system(系统)
	system("mode con cols=50 lines=20");//cols行 lines列  con->console(控制台)
	system("title 贪吃蛇");//title(标题)
	system("pause");//pause(停顿)
	return 0;
}

控制台坐标 COORD 

COORD 结构 - Windows Console | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/console/coord-str结构体是用来表示控制台屏幕缓冲区上一个字符坐标的。它是windows.h头文件中定义的一个数据结构,主要用于与控制台相关的编程操作,如读写屏幕缓冲区、移动光标位置等。以下是对 COORD 结构体的详细介绍:

typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD;

 成员

  • SHORT X: 表示字符在缓冲区中的水平位置(列数)。X轴的正方向是从左到右,因此X值越大,字符在屏幕上的位置越靠右。

  • SHORT Y: 表示字符在缓冲区中的垂直位置(行数)。Y轴的正方向是从上到下,因此Y值越大,字符在屏幕上的位置越靠下。

坐标系

如您所述,COORD 结构体使用的坐标系以缓冲区的顶部左侧单元格为原点 (0, 0)。这意味着:

  • 当 X = 0 且 Y = 0 时,对应的是缓冲区左上角的第一个单元格。
  • 随着 X 值增大,字符位置沿水平方向向右移动。
  • 随着 Y 值增大,字符位置沿垂直方向向下移动。


GetStdHandle 

GetStdHandle 函数 - Windows Console | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/console/getstdhandle

  GetStdHandle 是 Windows API 中的一个函数,用于获取与标准输入、标准输出或标准错误设备关联的句柄。这些标准设备是操作系统为进程预定义的输入(通常是键盘)、输出(通常是显示器)和错误输出(也是显示器,但通常与标准输出区分显示)通道。通过获取这些设备的句柄,程序员可以利用其他 Windows API 函数对这些设备进行读写操作或其他相关控制。

函数原型

HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

参数

  • nStdHandle: 一个 DWORD 类型的参数,指定要获取的标准设备句柄。可取以下常量值之一:

    • STD_INPUT_HANDLE (常量值 -10):获取标准输入设备(通常为键盘)的句柄。
    • STD_OUTPUT_HANDLE (常量值 -11):获取标准输出设备(通常为显示器)的句柄。
    • STD_ERROR_HANDLE (常量值 -12):获取标准错误设备(通常为显示器,与标准输出区分显示)的句柄。

返回值

  • 如果成功,函数返回与指定标准设备关联的有效句柄。
  • 如果失败,函数返回 INVALID_HANDLE_VALUE。可通过调用 GetLastError() 函数获取具体的错误代码。

什么是句柄?

句柄(Handle)是计算机科学中的一个概念,它在Windows操作系统环境中被广泛使用。简单来说,句柄是一种抽象的、唯一的标识符,用于指向并操作系统内特定的对象或资源,如文件、窗口、进程、设备等。为了帮助您更好地理解句柄的概念,我们可以用一个生动的比喻来解释:

假设您养了一群鸭子,每只鸭子都有一个独特的脚环,上面刻有唯一编号。这些编号就像鸭子的“句柄”,用来标识和区分每一只鸭子:

  1. 唯一标识:每个鸭子脚环上的编号都是独一无二的,就像句柄标识系统内的每一个对象一样。通过这个编号(句柄),您可以快速准确地识别出特定的鸭子(对象)。

  2. 操作鸭子:有了鸭子的编号(句柄),您可以对它进行各种操作,比如喂食、清理、记录生长情况等。同样,程序员通过句柄可以对系统对象进行读取、写入、删除、移动、控制属性等各种操作。例如,您可以使用句柄来读取文件内容、移动窗口位置、发送消息给进程等。

  3. 隐藏复杂性:鸭子脚环上的编号简化了您对鸭群的管理,您不需要深入了解每只鸭子的具体生理特征或生活习性,只需记住编号就能进行操作。同样,句柄隐藏了操作系统内部对象的复杂实现细节,程序员只需要通过句柄与系统交互,无需关心对象在内存中的具体位置、数据结构等底层信息。

  4. 生命周期管理:如果某只鸭子不幸去世或被卖出,它的编号(句柄)就不再有效,无法用来操作任何鸭子。在计算机系统中,当某个对象被删除、关闭或释放后,其对应的句柄也会失效,试图通过无效句柄操作对象会导致错误。因此,程序员需要注意句柄的有效性,适时释放不再使用的句柄,避免资源泄漏。

示例用法
//STD_INPUT_HANDLE--输入设备
//STD_OUTPUT_HANDLE--输出设备
//STD_ERROR_HANDLE--错误设备
int main(){
int main() {
	//获得句柄
	HANDLE output = NULL;
	output = GetStdHandle(STD_OUTPUT_HANDLE);
return 0;
}

GetConsoleCursorInfo 

GetConsoleCursorInfo 函数 - Windows Console | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/console/getconsolecursorinfo        GetConsoleCursorInfo 是 Windows API 中的一个函数,用于获取与指定控制台屏幕缓冲区关联的光标的可见性状态和大小信息。这个函数对于控制台应用程序而言非常重要,因为它允许程序员查询和可能之后修改控制台光标的行为和外观。以下是 GetConsoleCursorInfo 函数的详细说明:

函数原型

BOOL WINAPI GetConsoleCursorInfo(
  _In_     HANDLE hConsoleOutput,
  _Out_    PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

参数

  • hConsoleOutput: 一个 HANDLE 类型的参数,指定要查询的控制台屏幕缓冲区的句柄。通常通过 GetStdHandle(STD_OUTPUT_HANDLE) 获取标准输出设备的句柄,或者直接使用创建的控制台屏幕缓冲区句柄。

  • lpConsoleCursorInfo: 一个指向 CONSOLE_CURSOR_INFO 结构体的指针。该结构体用于接收光标的属性信息。

CONSOLE_CURSOR_INFO 结构体

typedef struct _CONSOLE_CURSOR_INFO {
  DWORD  dwSize;          // 光标的宽度,以百分比形式表示(1%-100%)
  BOOL   bVisible;        // 光标的可见性状态(TRUE/FALSE)
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

返回值

  • 如果函数成功,返回非零值(TRUE)。
  • 如果函数失败,返回零值(FALSE)。可以通过调用 GetLastError() 函数获取具体的错误代码。

用途

使用 GetConsoleCursorInfo 函数,您可以:

  • 查询当前控制台光标的可见性状态(是否显示光标)。
  • 查询光标的宽度(以百分比形式表示,影响光标在屏幕上的粗细)。
     

SetConsoleCursorInfo 

SetConsoleCursorInfo 函数 - Windows Console | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/console/setconsolecursorinfo  SetConsoleCursorInfo 是 Windows API 中的一个函数,用于设置与指定控制台屏幕缓冲区关联的光标的可见性状态和大小。这个函数与 GetConsoleCursorInfo 相对应,提供了修改控制台光标行为和外观的能力。以下是 SetConsoleCursorInfo 函数的详细说明:

函数原型

BOOL WINAPI SetConsoleCursorInfo(
  _In_     HANDLE hConsoleOutput,
  _In_     const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

参数

  • hConsoleOutput: 一个 HANDLE 类型的参数,指定要设置的控制台屏幕缓冲区的句柄。通常通过 GetStdHandle(STD_OUTPUT_HANDLE) 获取标准输出设备的句柄,或者直接使用创建的控制台屏幕缓冲区句柄。

  • lpConsoleCursorInfo: 一个指向 CONSOLE_CURSOR_INFO 结构体的指针,该结构体包含要设置的光标属性信息。

示例用法
int main() {
	//获得句柄
	HANDLE output = NULL;
	output = GetStdHandle(STD_OUTPUT_HANDLE);

	//光标信息
	CONSOLE_CURSOR_INFO cursor_info = {0};

	//获取output句柄相关的控制台信息,存放在cursor_info里面
	GetConsoleCursorInfo(output, &cursor_info);
	cursor_info.dwSize = 100;//修改光标的占比50%(总100%)
	cursor_info.bVisible = FALSE;//修改光标的可见性(TRUE/FALSE)
	SetConsoleCursorInfo(output, &cursor_info);//设置修改的信息
	
	return 0;
}

SetConsoleCursorPosition 

SetConsoleCursorPosition 函数 - Windows Console | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/console/setconsolecursorposition  SetConsoleCursorPosition 是 Windows API 中的一个函数,用于设置与指定控制台屏幕缓冲区关联的光标位置。通过调用此函数,程序员可以精确地控制光标在控制台屏幕上的位置,这对于在控制台上绘制图形、定位文本输出等操作至关重要。以下是 SetConsoleCursorPosition 函数的详细说明:

函数原型

BOOL WINAPI SetConsoleCursorPosition(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwCursorPosition
);

参数

  • hConsoleOutput: 一个 HANDLE 类型的参数,指定要设置光标位置的控制台屏幕缓冲区的句柄。通常通过 GetStdHandle(STD_OUTPUT_HANDLE) 获取标准输出设备的句柄,或者直接使用创建的控制台屏幕缓冲区句柄。

  • dwCursorPosition: 一个 COORD 结构体,表示新的光标位置。COORD 结构体包含两个成员:X 表示水平位置(列数),Y 表示垂直位置(行数)。坐标系原点位于缓冲区的左上角,即 (0, 0)

返回值

  • 如果函数成功,返回非零值(TRUE)。
  • 如果函数失败,返回零值(FALSE)。可以通过调用 GetLastError() 函数获取具体的错误代码。
示例用法
#include<stdio.h>
void set_pos(int x,int y) {

	//给一个坐标
	COORD pos = { x,y };
	//获取句柄
	HANDLE output = NULL;
	output = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置坐标位置
	SetConsoleCursorPosition(output, pos);//设置控制台光标位置
}
//改变光标位置
int main() {
	set_pos(5,6);
	
	printf("hahah");
	set_pos(10, 20);

	printf("hahah");
	return 0;
}

getAsyncKeyState


getAsyncKeyState 函数 (winuser.h) - Win32 apps | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getasynckeystate     GetAsyncKeyState 是 Windows API 提供的一个函数,用于异步检测键盘按键的状态。它允许程序查询某个键在当前或最近一次消息循环中是否被按下,而无需阻塞等待用户的按键事件。这种非阻塞的键盘状态检查对于实时响应用户输入、实现热键功能或其他需要即时了解键盘按键情况的应用场景非常有用。下面是 GetAsyncKeyState 函数的详细说明:

函数原型

SHORT WINAPI GetAsyncKeyState(
  _In_ int vKey
);

参数:

  • vKey: 一个整数值,表示要查询的虚拟键码。虚拟键码是系统用来标识键盘上每个键的标准代码,例如 VK_A 表示字母 'A' 键,VK_SPACE 表示空格键,等等。完整的虚拟键码列表可以在 Windows 头文件 winuser.h 中找到,或者查阅 Microsoft 文档。

返回值:

GetAsyncKeyState 函数返回一个短整型值(SHORT),其中包含按键状态信息。返回值可以按位分解,具有以下含义:

  • 低16位: 如果该键在当前消息队列中有一个按下消息,或者自从上次调用 GetAsyncKeyState 以来该键被按住,那么最低位(bit 0)被设置为1,表示按键处于按下状态。否则,该位为0,表示按键未被按下。

  • 高16位: 如果该键在当前消息队列中有至少一个按下消息,且该消息尚未被读取,那么高16位被设置为一个非零值。这通常用于区分新按键事件与持续按键状态。对于大多数应用,只需关注低16位即可。

示例用法
//检测数字按键

#include <stdio.h>
#include <windows.h>
#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)
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;
}

SetConsoleTextAttribute

SetConsoleTextAttribute 函数 - Windows Console | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/console/setconsoletextattribute

  SetConsoleTextAttribute 是 Windows API 提供的一个函数,用于设置控制台窗口中输出文本的颜色和背景色。通过调用此函数,程序员可以改变控制台窗口中后续输出文本的显示样式,以增强控制台应用程序的可读性或美观度。以下是 SetConsoleTextAttribute 函数的详细说明:

函数原型

BOOL WINAPI SetConsoleTextAttribute(
  _In_ HANDLE hConsoleOutput,
  _In_ WORD   wAttributes
);

参数:

  • hConsoleOutput: 一个 HANDLE 类型的参数,指定要设置文本属性的控制台屏幕缓冲区的句柄。通常通过 GetStdHandle(STD_OUTPUT_HANDLE) 获取标准输出设备的句柄,或者直接使用创建的控制台屏幕缓冲区句柄。

  • wAttributes: 一个 WORD 类型的值,表示要设置的文本颜色和背景色组合。这个值由两个部分组成:低四位表示前景色(文本颜色),高四位表示背景色。每个部分可以是以下预定义的常量之一,这些常量定义在 wincon.h 头文件中:

    • 前景色(文本颜色)常量:

      • FOREGROUND_BLACK
      • FOREGROUND_BLUE
      • FOREGROUND_GREEN
      • FOREGROUND_RED
      • FOREGROUND_INTENSITY (加亮效果,可以与以上颜色常量组合使用)
      • FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)
      • FOREGROUND_MAGENTA (FOREGROUND_RED | FOREGROUND_BLUE)
      • FOREGROUND_CYAN (FOREGROUND_BLUE | FOREGROUND_GREEN)
      • FOREGROUND_WHITE (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
    • 背景色常量:

      • BACKGROUND_BLACK
      • BACKGROUND_BLUE
      • BACKGROUND_GREEN
      • BACKGROUND_RED
      • BACKGROUND_INTENSITY (加亮效果,可以与以上颜色常量组合使用)
      • BACKGROUND_YELLOW (BACKGROUND_RED | BACKGROUND_GREEN)
      • BACKGROUND_MAGENTA (BACKGROUND_RED | BACKGROUND_BLUE)
      • BACKGROUND_CYAN (BACKGROUND_BLUE | BACKGROUND_GREEN)
      • BACKGROUND_WHITE (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)

返回值:

  • 如果函数成功,返回非零值(TRUE)。
  • 如果函数失败,返回零值(FALSE)。可以通过调用 GetLastError() 函数获取具体的错误代码。

示例用法

#include <windows.h>

int main() {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    // 设置前景色为红色,背景色为绿色,无加亮效果
    SetConsoleTextAttribute(hConsole, FOREGROUND_RED | BACKGROUND_GREEN);

    printf("Hello, colored console!\n");

    // 恢复默认颜色
    SetConsoleTextAttribute(hConsole, FOREGROUND_WHITE | BACKGROUND_BLACK);

    return 0;
}

这边在扩展一个固定控制台的几个api函数

GetConsoleWindow 函数 - Windows Console | Microsoft Learnicon-default.png?t=N7T8https://learn.microsoft.com/zh-cn/windows/console/getconsolewindow

LONG_PTR windowStyle = GetWindowLongPtr(consoleWindow, GWL_STYLE);
// 禁止用户调整窗口大小,移除WS_SIZEBOX、WS_MAXIMIZEBOX、WS_MINIMIZEBOX样式
SetWindowLongPtr(consoleWindow, GWL_STYLE, windowStyle & ~(WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX));//移除按钮和边框这样就不能改变大小
// 应用新的窗口样式
SetWindowPos(consoleWindow, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

步骤一:获取控制台窗口句柄

首先,我们需要获取当前活动的控制台窗口句柄。在Windows API中,每个窗口都由一个唯一的句柄(HWND)标识。通过调用 GetConsoleWindow() 函数,可以轻松获取与当前控制台关联的窗口句柄。

HWND consoleWindow = GetConsoleWindow();

步骤二:获取当前窗口样式

接下来,我们需要了解当前控制台窗口的样式。Windows窗口样式是一组标志,用于定义窗口的外观和行为。这些样式通过调用 GetWindowLongPtr() 函数并传递 GWL_STYLE 参数来获取。

LONG_PTR windowStyle = GetWindowLongPtr(consoleWindow, GWL_STYLE);

步骤三:移除允许调整大小的样式

要禁止用户调整窗口大小,需要移除与窗口大小调整相关的样式标志。这些标志包括:

  • WS_SIZEBOX:表示窗口具有可调整大小的边框。
  • WS_MAXIMIZEBOX:表示窗口具有最大化按钮。
  • WS_MINIMIZEBOX:表示窗口具有最小化按钮。

通过按位与(&~)操作符,我们可以清除这些样式标志,得到一个新的窗口样式值。

LONG_PTR fixedWindowStyle = windowStyle & ~(WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX);

步骤四:应用新的窗口样式

最后,使用 SetWindowLongPtr() 函数将更新后的窗口样式应用到控制台窗口。然后,调用 SetWindowPos() 函数,指定SWP_FRAMECHANGED标志以强制系统重新绘制窗口框架,从而反映样式更改。

SetWindowLongPtr(consoleWindow, GWL_STYLE, fixedWindowStyle);
SetWindowPos(consoleWindow, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

这两行代码分别执行了设置控制台窗口新样式和更新其窗口位置及外观的操作。下面对这两行代码进行详细解析:

SetWindowLongPtr(consoleWindow, GWL_STYLE, fixedWindowStyle);

解析:

  • consoleWindow: 这是之前通过 GetConsoleWindow() 函数获取的控制台窗口句柄,表示我们要操作的目标窗口。

  • GWL_STYLE: 这是一个常量,用于指定我们要修改的窗口属性类型。在这里,它表示我们要修改的是窗口的样式(style),而非扩展样式(extended style)或其他类型的属性。

  • fixedWindowStyle: 这是在前一步计算得出的新窗口样式值,其中已移除了允许用户调整窗口大小的样式标志(WS_SIZEBOXWS_MAXIMIZEBOX 和 WS_MINIMIZEBOX)。将其作为参数传入,意在将控制台窗口的样式更新为不允许用户调整大小的状态。

这行代码的整体作用是,将经过修改的窗口样式值 fixedWindowStyle 应用到 consoleWindow 所指向的控制台窗口上,从而实现禁止窗口大小调整的功能。

SetWindowPos(consoleWindow, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

解析:

  • consoleWindow: 同样是控制台窗口的句柄,指示本次操作的目标窗口。

  • NULL: 这个参数代表窗口将保持其当前的父窗口或子窗口关系不变。如果需要将窗口置于某个特定窗口之下,这里应传入该父窗口的句柄。

  • (0, 0, 0, 0): 这四个整数分别代表新窗口的位置(X坐标、Y坐标)和尺寸(宽度、高度)。由于我们不希望移动或改变窗口大小,所以这些值都被设为0。实际操作时,Windows会忽略这些值,因为接下来我们将指定特定的标志来控制窗口是否移动或调整大小。

  • SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED: 这些是组合使用的窗口位置和尺寸标志(flags),用来指定此次 SetWindowPos() 调用的具体行为:

    • SWP_NOMOVE: 表示不要移动窗口。即使指定了位置坐标(X, Y),这个标志也会让Windows忽略它们,保持窗口当前位置不变。

    • SWP_NOSIZE: 表示不要改变窗口大小。即使指定了尺寸(宽度、高度),这个标志也会让Windows忽略它们,保持窗口当前大小不变。

    • SWP_NOZORDER: 表示不要改变窗口在Z轴(堆叠顺序)上的位置,即不要改变窗口相对于其他同级窗口的前后顺序。

    • SWP_FRAMECHANGED: 这是关键的标志,它告诉Windows窗口的非客户区(如标题栏、菜单、边框等)已经发生了变化,需要重新绘制。在这里,由于我们刚刚修改了窗口样式以去除调整大小的相关元素,因此需要触发这一重绘操作,使窗口立刻呈现出新的样式效果。

好了我们现在所有的需要用的API函数全部都介绍完了,接下来进入正题

贪吃蛇

宽字符

为什么会出现宽字符嘞?

宽字符的来历:从ASCII到Unicode,C语言中的宽字符处理-CSDN博客宽字符作为Unicode在C语言中的具体实现形式,极大地扩展了编程语言对全球多语种字符的支持能力。虽然在实际应用中还需考虑编码转换、平台差异等问题,但宽字符无疑为构建跨语言、跨文化的软件系统奠定了坚实基础。理解并熟练运用宽字符,是现代C程序员必备的技能之一。https://blog.csdn.net/2302_78381559/article/details/138158952?spm=1001.2014.3001.5502请看这篇博客,看完这篇博客我们需要做的是本地化

#include<locale.h>
#include<stdio.h>
int main() {
	char* set;
	set = setlocale(LC_ALL,"C");//C语言本来的模式
	printf("%s\n", set);
	set = setlocale(LC_ALL, "");//本地模式:Chinese (Simplified)_China.936
	printf("%s", set);
	return 0;
}
setlocale 函数
char *setlocale(int category, const char *locale);
  • 功能:设置或查询程序的本地化类别。category 参数指定要设置或查询的类别(如LC_ALL、LC_CTYPE、LC_NUMERIC等),locale 参数指定新的本地化设置(如"en_US.UTF-8"、"zh_CN.GBK"等)或传入空指针以查询当前设置。

  • 返回值:若成功设置新的本地化,返回指向新设置的字符串指针;若查询当前设置,返回指向当前设置的字符串指针;若失败,返回空指针。

本地化类别常量

  • LC_ALL:影响所有本地化类别。
  • LC_COLLATE:影响字符排序规则。
  • LC_CTYPE:影响字符分类(如字母、数字、空白等)和转换(如大小写转换)。
  • LC_MONETARY:影响货币格式化。
  • LC_NUMERIC:影响数字、小数点和千位分隔符的格式。
  • LC_TIME:影响日期和时间的格式化。
宽字符的打印
int main() {
	setlocale(LC_ALL, "");
	//窄字符
	char a = 'a';
	char b = 'b';
	printf("%c %c\n", a, b);
	//宽字符
	wchar_t c = L'●';
	wchar_t d = L'▲';
	wprintf(L"%lc\n", c);
	wprintf(L"%lc\n", d);
}

地图

数据结构设计

链表结构

//创建蛇身节点类型
typedef struct snakeNode {
	//坐标
	int x;
	int y;
	//指向下一个节点的指针
	struct snakeNode* next;
}snakeNode,*psnakeNode_Hand;

要管理整条贪吃蛇,我们再封装⼀个Snake的结构来维护整条贪吃蛇:

/贪吃蛇 
typedef struct Snake
{
	psnakeNode_Hand Head;//指向蛇头的指针
	psnakeNode_Hand food;//指向食物的指针
	enum Direction dir;//蛇的方向
	enum Game_State state;//游戏状态
	int food_scores;//一个食物的分数
	int sum;//总分
	int sleep_time;//蛇的速度:休息时间(时间越短,速度越快; 时间越长,速度越慢)
}Snake, * psnake;

蛇的方向

//蛇的方向
enum Direction
{
	UP = 1,//向上
	DOWN,//向下
	LEFT,//向左
	RIGHT//向右
};

游戏状态

//游戏状态
enum Game_State
{
	OK,//正常
	WALL,//撞墙
	TOUCH_YOURSELF,//触碰到自己
	END//结束
};

流程图

接下来就是我们的手撕代码环节

main.c(解析全在注释中

#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
void text() {
	int ch = 0;
	do
	{
		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化
		Game_start(&snake);
		//运行
		Game_run(&snake);
		//结束
		Game_end(&snake);
		set_pos(20,15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		while (getchar() != '\n');
		//getchar();
		
	} while (ch == 'Y' || ch =='y');
	set_pos(0, 27);
}
int main() {
	//适配本地环境
	setlocale(LC_ALL, "");
	text();
	
	return 0;
}

Game_start

//隐藏光标+控制台大小
static void set_window() {
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//获取句柄
	HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
	//定义光标结构体来存储当前句柄的光标信息
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	//获取句柄光标信息
	GetConsoleCursorInfo(output, &cursor_info);
	//隐藏光标
	cursor_info.bVisible = FALSE;
	SetConsoleCursorInfo(output, &cursor_info);
	//设置窗口固定大小
	// 获取当前控制台窗口的句柄
	HWND consoleWindow = GetConsoleWindow();
	// 获取当前窗口的样式
	LONG_PTR windowStyle = GetWindowLongPtr(consoleWindow, GWL_STYLE);
	// 禁止用户调整窗口大小,移除WS_SIZEBOX、WS_MAXIMIZEBOX、WS_MINIMIZEBOX样式
	SetWindowLongPtr(consoleWindow, GWL_STYLE, windowStyle & ~(WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX));//移除按钮和边框这样就不能改变大小
	// 应用新的窗口样式
	SetWindowPos(consoleWindow, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
//定位光标
void set_pos(int x,int y) {

	//给一个坐标
	COORD pos = { x,y };
	//获取句柄
	HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置坐标位置
	SetConsoleCursorPosition(output, pos);//设置控制台光标位置
}
//欢迎开始界面
static void wecometogame() {
	set_pos(36, 10);
	wprintf(L"欢迎来到贪吃蛇游戏\n");
	set_pos(36, 25);
	system("pause");
	system("cls");
	set_pos(40, 10);
	wprintf(L"游戏介绍\n");
	set_pos(32, 12);
	wprintf(L"用↑ ↓ ← →键位控制方向,F3(加速) F4(减速)\n");
	set_pos(32, 14);
	wprintf(L"加速可得到更高的分数\n");
	set_pos(36, 25);
	system("pause");
	system("cls");

}
//修改控制台字符颜色
static void Textcolor(WORD   wAttributes) {
	//获取句柄
	HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(output, wAttributes);

}
void Drawmap()
{
    // 设置文本颜色为红色
    Textcolor(FOREGROUND_RED);

    int i = 0;

    // 打印顶部墙壁(一行)
    for (i = 0; i < 29; i++)
    {
        // 输出代表墙壁的字符 'WALL_l',假设它是某种墙的图形符号
        wprintf(L"%lc", WALL_l);
    }

    // 设置光标位置到屏幕左下角(第0列,第26行)
    set_pos(0, 26);

    // 打印底部墙壁(一行)
    for (i = 0; i < 29; i++)
    {
        // 同样输出墙壁字符 'WALL_l'
        wprintf(L"%lc", WALL_l);
    }

    // 遍历从第0行至第25行(包含两端),打印左侧墙壁
    for (i = 0; i <= 25; i++)
    {
        // 设置光标位置到指定行(i)的起始列(0)
        set_pos(0, i);

        // 输出墙壁字符 'WALL_l'
        wprintf(L"%lc", WALL_l);
    }

    // 遍历同样的行数(0至25),打印右侧墙壁
    for (i = 0; i <= 25; i++)
    {
        // 设置光标位置到指定行(i)的末尾列(56)
        set_pos(56, i);

        // 输出墙壁字符 'WALL_l'
        wprintf(L"%lc", WALL_l);
    }
}
//创建蛇
void Create_snake(psnake ps)
{
    // 初始化当前处理的节点指针为空
    psnakeNode_Hand cur = NULL;

    // 循环3次,为蛇创建3个初始节点(即长度为3的身体)
    for (int i = 0; i < 3; i++)
    {
        // 动态申请内存空间创建一个新的蛇节点
        cur = (psnakeNode_Hand)malloc(sizeof(snakeNode));
        if (cur == NULL)
        {
            // 如果内存分配失败,输出错误信息并退出函数
            perror("Create_snake::malloc");
            return;
        }

        // 设置新节点坐标,根据宽度调整间隔,确保身体不重叠
        cur->next = NULL;
        cur->x = pos_x + i * 2; // 每增加一个节点,横坐标向右移动两格
        cur->y = pos_y;

        // 头插法将新节点添加到蛇链表头部
        if (ps->Head == NULL)
        {
            // 当链表为空时,将新节点作为链表头
            ps->Head = cur;
        }
        else
        {
            // 当链表非空时,将新节点指向原链表头,然后更新链表头为新节点
            cur->next = ps->Head;
            ps->Head = cur;
        }
    }

    // 设置贪吃蛇的身体部分为蓝色,并打印在控制台上
    Textcolor(FOREGROUND_BLUE);
    cur = ps->Head;
    while (cur != NULL)
    {
        // 根据节点坐标设置光标位置并打印蛇身字符BODY
        set_pos(cur->x, cur->y);
        wprintf(L"%c", BODY);

        // 移动到下一个节点
        cur = cur->next;
    }

    // 初始化贪吃蛇其他属性
    ps->dir = RIGHT;         // 初始方向为向右
    ps->food_scores = 10;    // 每吃到一颗食物得10分
    ps->sleep_time = 200;    // 初始移动间隔时间
    ps->state = OK;          // 蛇的状态设为正常
    ps->sum = 0;             // 初始化得分或者其他累计值为0
}

//创建食物
void Create_food(psnake ps)
{
    int x, y; // 声明随机生成的食物坐标变量

retry_generation:
    // 生成随机坐标,限定在有效范围内,并确保x坐标为偶数
    do
    {
        x = rand() % 53 + 2; // x坐标范围:2至54,取模保证不会超出边界
        y = rand() % 25 + 1; // y坐标范围:1至25
    } while (x % 2 != 0); // 确保x坐标是偶数,符合屏幕布局要求

    // 遍历蛇的所有节点,检查新生成的坐标是否与蛇体坐标冲突
    psnakeNode_Hand cur = ps->Head;
    while (cur)
    {
        if (cur->x == x && cur->y == y) // 发现坐标重合
        {
            goto retry_generation; // 重新生成新的坐标,避免与蛇体重叠
        }
        cur = cur->next; // 移动到下一个节点
    }

    // 分配内存空间创建食物节点
    psnakeNode_Hand pfood = (psnakeNode_Hand)malloc(sizeof(snakeNode));
    if (pfood == NULL)
    {
        // 如果内存分配失败,输出错误信息并结束函数
        perror("Create_food::malloc");
        return;
    }
    else
    {
        // 设置食物的颜色为绿色
        Textcolor(FOREGROUND_GREEN);

        // 将随机生成的坐标赋值给食物节点
        pfood->x = x;
        pfood->y = y;

        // 设置光标位置并在相应坐标上打印食物符号
        set_pos(pfood->x, pfood->y);
        wprintf(L"%lc", FOOD);

        // 将新生成的食物节点赋值给全局的游戏食物指针
        ps->food = pfood;
    }
}
//初始化
void Game_start(psnake ps) {
	//时间戳
	srand((unsigned int)time(NULL));
//--隐藏光标+控制台大小--
	set_window();
//--打印开始界面+功能介绍--
	wecometogame();
//--绘制地图--
	Drawmap();
//--创建蛇--//--设置游戏相关信息--
	Create_snake(ps);
//--创建食物--
	Create_food(ps);
}

Game_run

//游戏提示
void Game_prompt() {
	set_pos(75, 7);
	wprintf(L"%ls", L"《游戏提示》\n");
	set_pos(64, 10);
	wprintf(L"%ls", L"<不能穿墙,不能咬到自己>\n");
	set_pos(64, 12);
	wprintf(L"%ls", L"<用↑.↓.←.→分别控制蛇的移动>\n");
	set_pos(64, 14);
	wprintf(L"%ls", L"<F3 为加速,F4 为减速>\n");
	set_pos(64, 16);
	wprintf(L"%ls", L"<ESC :退出游戏.space:暂停游戏>\n");
	set_pos(80, 18);
	wprintf(L"%ls",L"--普通小青年\n");
	
}

//判断是否遇到食物
int Nextfood(psnakeNode_Hand pn, psnake ps) {
	
	return (pn->x == ps->food->x && pn->y == ps->food->y);//都满足就返回1,否者返回0
}

//吃掉食物
void Eatfood(psnakeNode_Hand pn, psnake ps) {
	//头插
	pn->next = ps->Head;
	ps->Head = pn;
	
	psnakeNode_Hand cur = ps->Head;
	while (cur)
	{
		Textcolor(FOREGROUND_BLUE);
		set_pos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->sum += ps->food_scores;//吃掉一个食物加分
	free(ps->food);//销毁食物
	Create_food(ps);//再次生成一个食物
}

//没有食物
void Nofood(psnakeNode_Hand pn, psnake ps) {
	//psnakeNode_Hand pn -- 下一个节点地址
	//psnake ps 维护蛇的指针
	
	//头插
	pn->next = ps->Head;
	ps->Head = pn;

	psnakeNode_Hand cur = ps->Head;
	
	//找到倒数第二个节点地址
	while (cur->next->next)
	{
		set_pos(cur->x,cur->y);
		Textcolor(FOREGROUND_BLUE);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	Textcolor(FOREGROUND_GREEN);
	set_pos(cur->next->x,cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;

}

//判断撞墙
void KillByWall(psnake ps) {
	//判断头部是否和我们的墙的坐标一样
	if (
		(ps->Head->x == 0) ||
		(ps->Head->x == 56)||
		(ps->Head->y == 0)||
		(ps->Head->y == 26)
		)
	{
		//游戏状态
		ps->state = WALL;
		return 1;

	}
	return 0;
}

//判断撞到自己
void KillBySelf(psnake ps) {
	//遍历坐标如果有自己子节点的坐标
	psnakeNode_Hand cur = ps->Head->next;//遍历除了头节点的其他节点所以我们要ps->Head->next,也就是第二个节点开始遍历
	while (cur)
	{
		if (
			(ps->Head->x == cur->x)
			&&(ps->Head->y == cur->y)
			) {

			ps->state = TOUCH_YOURSELF;
			return 1;
		}
		cur = cur->next;
	}
	return 0;
}

void SnakeMove(psnake ps)
{
    // 动态分配内存创建一个新的蛇节点
    psnakeNode_Hand pNextNode = (psnakeNode_Hand)malloc(sizeof(snakeNode));
    if (pNextNode == NULL)
    {
        // 内存分配失败时,输出错误信息并返回
        perror("SnakeMove::malloc");
        return;
    }

    // 根据蛇的移动方向确定下一个节点的坐标
    switch (ps->dir)
    {
        case UP:
            pNextNode->x = ps->Head->x;
            pNextNode->y = ps->Head->y - 1; // 向上移动
            break;
        case DOWN:
            pNextNode->x = ps->Head->x;
            pNextNode->y = ps->Head->y + 1; // 向下移动
            break;
        case LEFT:
            pNextNode->x = ps->Head->x - 2; // 左移两个单位,以适应宽字符
            pNextNode->y = ps->Head->y;
            break;
        case RIGHT:
            pNextNode->x = ps->Head->x + 2; // 右移两个单位,以适应宽字符
            pNextNode->y = ps->Head->y;
            break;
    }

    // 判断蛇的下一个节点位置是否为食物
    if (Nextfood(pNextNode, ps)) // 自定义函数,判断下一个节点是否为食物
    {
        Eatfood(pNextNode, ps);   // 自定义函数,处理蛇吃到食物的情况
    }
    else
    {
        Nofood(pNextNode, ps);    // 自定义函数,处理蛇未吃到食物的情况
    }

    // 判断蛇是否撞到墙壁
    KillByWall(ps);

    // 判断蛇是否撞到自己的身体
    KillBySelf(ps);
}

//游戏暂停
void Game_stop() {
	
	while (1)
	{
		if (KEY_PRESS(VK_SPACE)) {
			break;
		}
		Sleep(200);
	}
}

void Game_run(psnake ps)
{
    // 显示游戏提示信息
    Game_prompt();

    // 主循环
    do
    {
        // 打印当前得分和食物的分值
        set_pos(64, 9);
        printf("总分:%2d ", ps->sum);
        printf("食物分值:%2d\n", ps->food_scores);

        // 处理玩家按键操作
        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))
        {
            // 暂停游戏
            Game_stop();
        }
        else if (KEY_PRESS(VK_ESCAPE))
        {
            // 结束游戏
            ps->state = END;
        }
        else if (KEY_PRESS(VK_F3))
        {
            // 加速游戏
            if (ps->sleep_time >= 80)
            {
                ps->sleep_time -= 30; // 减少延迟时间,加快游戏速度
                ps->food_scores += 2; // 提高食物分值
            }
        }
        else if (KEY_PRESS(VK_F4))
        {
            // 减速游戏
            if (ps->food_scores > 2)
            {
                ps->sleep_time += 30; // 增加延迟时间,减慢游戏速度
                ps->food_scores -= 2; // 降低食物分值
            }
        }

        // 暂停一段时间(根据游戏速度)
        Sleep(ps->sleep_time);

        // 蛇的实际移动处理
        SnakeMove(ps);

    } while (ps->state == OK); // 若游戏状态为正常,则继续循环

    // 当游戏状态不是OK时,意味着游戏已结束或者暂停
}

Game_end

// 定义函数Game_end,参数为指向蛇结构体的指针ps
void Game_end(psnake ps) 
{
    // 获取蛇头节点的指针
    psnakeNode_Hand cur = ps->Head;

    // 设置光标位置到屏幕(24行,12列)
    set_pos(24, 12);

    // 根据蛇的状态输出相应的结束信息
    if (ps->state == END) 
    {
        printf("你主动退出游戏\n");
    }
    else if (ps->state == WALL)
    {
        printf("撞到墙了\n");
    }
    else if (ps->state == TOUCH_YOURSELF)
    {
        printf("咬到自己了\n");
    }

    // 遍历并释放蛇身所有节点占用的内存资源
    while (cur != NULL) 
    {
        // 创建临时指针指向当前节点
        psnakeNode_Hand del = cur;
        
        // 移动当前节点指针至下一个节点
        cur = cur->next;

        // 使用free函数释放del指向的节点内存
        free(del);
    }
}

好了给个全部代码

代码

main.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"
void text() {
	int ch = 0;
	do
	{
		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化
		Game_start(&snake);
		//运行
		Game_run(&snake);
		//结束
		Game_end(&snake);
		set_pos(20,15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		while (getchar() != '\n');
		//getchar();
		
	} while (ch == 'Y' || ch =='y');
	set_pos(0, 27);
}
int main() {
	//适配本地环境
	setlocale(LC_ALL, "");
	text();
	
	return 0;
}

text.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"text.h"

//隐藏光标+控制台大小
static void set_window() {
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//获取句柄
	HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
	//定义光标结构体来存储当前句柄的光标信息
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	//获取句柄光标信息
	GetConsoleCursorInfo(output, &cursor_info);
	//隐藏光标
	cursor_info.bVisible = FALSE;
	SetConsoleCursorInfo(output, &cursor_info);
	//设置窗口固定大小
	// 获取当前控制台窗口的句柄
	HWND consoleWindow = GetConsoleWindow();
	// 获取当前窗口的样式
	LONG_PTR windowStyle = GetWindowLongPtr(consoleWindow, GWL_STYLE);
	// 禁止用户调整窗口大小,移除WS_SIZEBOX、WS_MAXIMIZEBOX、WS_MINIMIZEBOX样式
	SetWindowLongPtr(consoleWindow, GWL_STYLE, windowStyle & ~(WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MINIMIZEBOX));//移除按钮和边框这样就不能改变大小
	// 应用新的窗口样式
	SetWindowPos(consoleWindow, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
//定位光标
void set_pos(int x,int y) {

	//给一个坐标
	COORD pos = { x,y };
	//获取句柄
	HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置坐标位置
	SetConsoleCursorPosition(output, pos);//设置控制台光标位置
}
//欢迎开始界面
static void wecometogame() {
	set_pos(36, 10);
	wprintf(L"欢迎来到贪吃蛇游戏\n");
	set_pos(36, 25);
	system("pause");
	system("cls");
	set_pos(40, 10);
	wprintf(L"游戏介绍\n");
	set_pos(32, 12);
	wprintf(L"用↑ ↓ ← →键位控制方向,F3(加速) F4(减速)\n");
	set_pos(32, 14);
	wprintf(L"加速可得到更高的分数\n");
	set_pos(36, 25);
	system("pause");
	system("cls");

}
//修改控制台字符颜色
static void Textcolor(WORD   wAttributes) {
	//获取句柄
	HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(output, wAttributes);

}
//地图打印
void Drawmap() {
	Textcolor(FOREGROUND_RED);
	int i = 0;
	for (i = 0 ; i < 29; i++)
	{
		wprintf(L"%lc", WALL_l);
	}

	set_pos(0, 26);
	for (i =0; i < 29; i++)
	{
		wprintf(L"%lc", WALL_l);
	}

	for ( i = 0; i <= 25; i++)
	{
		set_pos(0, i);
		wprintf(L"%lc", WALL_l);
	}

	for (i = 0; i <= 25; i++)
	{
		set_pos(56, i);
		wprintf(L"%lc", WALL_l);
	}
}
//创建蛇
void Create_snake(psnake ps) {
	psnakeNode_Hand cur = NULL;
	for (int i = 0; i < 3; i++)
	{
		//申请节点
		cur = (psnakeNode_Hand)malloc(sizeof(snakeNode));
		if (cur == NULL)
		{
			perror("Create_snake::malloc");
			return;
		}
		//设置坐标
		cur->next = NULL;
		cur->x = pos_x + i * 2;//*2是移动两格,避免重叠(宽字符)
		cur->y = pos_y;
		
		//利用头插将每个节点串在一起
		if (ps->Head == NULL)
		{
			ps->Head = cur;//将节点直接给到head的位置
		}
		else
		{
			cur->next = ps->Head;
			ps->Head = cur;
		}
	}
	//打印蛇的身体
	Textcolor(FOREGROUND_BLUE);
	cur = ps->Head;
	while (cur)
	{
		set_pos(cur->x, cur->y);
		wprintf(L"%c", BODY);
		cur = cur->next;
	}

	//初始化贪吃蛇的数据
	ps->dir = RIGHT;
	ps->food_scores = 10;
	ps->sleep_time = 200;
	ps->state = OK;
	ps->sum = 0;
	
}
//创建食物
void Create_food(psnake ps) {
	int x, y;
again:
	//生成随机坐标
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);//x坐标必须是2的倍数(偶数)

	//判断x,y的坐标和蛇是否冲突
	psnakeNode_Hand cur = ps->Head;
	while (cur)
	{
		if ((cur->x == x) && (cur->y == y)) {//将他们的坐标比较
			goto again;//相等直接重新生成坐标
		}
		cur = cur->next;//每一个节点
	}

	psnakeNode_Hand pfood = (psnakeNode_Hand)malloc(sizeof(snakeNode));
	if (pfood == NULL)
	{
		perror("Create_food::malloc");
		return;
	}
	else
	{
		//设置食物颜色
		Textcolor(FOREGROUND_GREEN);
		//赋值随机生成的坐标
		pfood->x = x;
		pfood->y = y;
		set_pos(pfood->x, pfood->y);
		wprintf(L"%lc", FOOD);//打印
		ps->food = pfood;
	}
	

}
//初始化
void Game_start(psnake ps) {
	//时间戳
	srand((unsigned int)time(NULL));
//--隐藏光标+控制台大小--
	set_window();
//--打印开始界面+功能介绍--
	wecometogame();
//--绘制地图--
	Drawmap();
//--创建蛇--//--设置游戏相关信息--
	Create_snake(ps);
//--创建食物--
	Create_food(ps);
}

//====================================================
//游戏提示
void Game_prompt() {
	set_pos(75, 7);
	wprintf(L"%ls", L"《游戏提示》\n");
	set_pos(64, 10);
	wprintf(L"%ls", L"<不能穿墙,不能咬到自己>\n");
	set_pos(64, 12);
	wprintf(L"%ls", L"<用↑.↓.←.→分别控制蛇的移动>\n");
	set_pos(64, 14);
	wprintf(L"%ls", L"<F3 为加速,F4 为减速>\n");
	set_pos(64, 16);
	wprintf(L"%ls", L"<ESC :退出游戏.space:暂停游戏>\n");
	set_pos(80, 18);
	wprintf(L"%ls",L"--普通小青年\n");
	
}

//判断是否遇到食物
int Nextfood(psnakeNode_Hand pn, psnake ps) {
	
	return (pn->x == ps->food->x && pn->y == ps->food->y);//都满足就返回1,否者返回0
}

//吃掉食物
void Eatfood(psnakeNode_Hand pn, psnake ps) {
	//头插
	pn->next = ps->Head;
	ps->Head = pn;
	
	psnakeNode_Hand cur = ps->Head;
	while (cur)
	{
		Textcolor(FOREGROUND_BLUE);
		set_pos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->sum += ps->food_scores;//吃掉一个食物加分
	free(ps->food);//销毁食物
	Create_food(ps);//再次生成一个食物
}

//没有食物
void Nofood(psnakeNode_Hand pn, psnake ps) {
	//psnakeNode_Hand pn -- 下一个节点地址
	//psnake ps 维护蛇的指针
	
	//头插
	pn->next = ps->Head;
	ps->Head = pn;

	psnakeNode_Hand cur = ps->Head;
	
	//找到倒数第二个节点地址
	while (cur->next->next)
	{
		set_pos(cur->x,cur->y);
		Textcolor(FOREGROUND_BLUE);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	Textcolor(FOREGROUND_GREEN);
	set_pos(cur->next->x,cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;

}

//判断撞墙
void KillByWall(psnake ps) {
	//判断头部是否和我们的墙的坐标一样
	if (
		(ps->Head->x == 0) ||
		(ps->Head->x == 56)||
		(ps->Head->y == 0)||
		(ps->Head->y == 26)
		)
	{
		//游戏状态
		ps->state = WALL;
		return 1;

	}
	return 0;
}

//判断撞到自己
void KillBySelf(psnake ps) {
	//遍历坐标如果有自己子节点的坐标
	psnakeNode_Hand cur = ps->Head->next;//遍历除了头节点的其他节点所以我们要ps->Head->next,也就是第二个节点开始遍历
	while (cur)
	{
		if (
			(ps->Head->x == cur->x)
			&&(ps->Head->y == cur->y)
			) {

			ps->state = TOUCH_YOURSELF;
			return 1;
		}
		cur = cur->next;
	}
	return 0;
}

//蛇的移动
void SnakeMove(psnake ps) {
	//创建下一个位置的节点
	psnakeNode_Hand pNextNode = (psnakeNode_Hand)malloc(sizeof(snakeNode));
	if (pNextNode ==NULL)
	{
		perror("SnakeMove::malloc");
		return;
	}
	//确定下一个节点的坐标
	switch (ps->dir) {
	case UP:
		pNextNode->x = ps->Head->x;
		pNextNode->y = ps->Head->y - 1;
		break;
	case DOWN:
		pNextNode->x = ps->Head->x;
		pNextNode->y = ps->Head->y + 1;
		break;
	case LEFT:
		pNextNode->x = ps->Head->x - 2;
		pNextNode->y = ps->Head->y;
		break;
	case RIGHT:
		pNextNode->x = ps->Head->x + 2;
		pNextNode->y = ps->Head->y;
		break;
	}
	//判断我们要走的下一个节点是不是食物
	if (Nextfood(pNextNode,ps))//写一个判断下一个节点是不是食物
	{
		Eatfood(pNextNode,ps);
	}
	else
	{
		Nofood(pNextNode, ps);
	}
	

	//判断撞墙
	KillByWall(ps);    
	//判断撞到自己
	KillBySelf(ps);
}

//游戏暂停
void Game_stop() {
	
	while (1)
	{
		if (KEY_PRESS(VK_SPACE)) {
			break;
		}
		Sleep(200);
	}
}

void Game_run(psnake ps) {
	//游戏提示
	Game_prompt();
	do
	{
		//打印分数和食物分值
		set_pos(64, 9);
		printf("总分:%2d ", ps->sum);
		printf("食物分值:%2d\n", ps->food_scores);
		
		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) )
		{
			//游戏暂停
			Game_stop();
		}
		else if (KEY_PRESS(VK_ESCAPE) )
		{
			//退出游戏
			ps->state = END;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (ps->sleep_time>=80) {
				ps->sleep_time -= 30;//时间越短数度越快
				ps->food_scores += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->food_scores>2) {
				ps->sleep_time += 30;//时间越大数度越慢
				ps->food_scores -= 2;
			}
		}

		Sleep(ps->sleep_time);
		//蛇的移动	
		SnakeMove(ps);

	} while (ps->state == OK);//游戏状态
}

//结束游戏
void Game_end(psnake ps) {
	psnakeNode_Hand cur = ps->Head;

	set_pos(24,12);
	if (ps->state == END) {
		printf("你主动退出游戏\n");
	}
	else if( ps->state == WALL)
	{
		printf("撞到墙了\n");
	}
	else if(ps->state == TOUCH_YOURSELF)
	{
		printf("咬到自己了\n");
	}

	//释放蛇身的节点
	while (cur)
	{
		psnakeNode_Hand del = cur;
		cur = cur->next;
		free(del);
	}

}

text.h

#pragma once
#include<locale.h>
#include<stdlib.h>
#include<stdbool.h>
#include<Windows.h>
#include<winuser.h>
#include<stdio.h>
#include<time.h>

#define WALL_l L'墙'
//#define BODY L'头'
#define BODY L'●'
//#define FOOD L'头'
#define FOOD L'▲'
#define pos_x 24
#define pos_y 5

#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk)&1)?1:0)
//蛇的方向
enum Direction
{
	UP = 1,//向上
	DOWN,//向下
	LEFT,//向左
	RIGHT//向右
};

//游戏状态
enum Game_State
{
	OK,//正常
	WALL,//撞墙
	TOUCH_YOURSELF,//触碰到自己
	END//结束
};

//创建蛇身节点类型
typedef struct snakeNode {
	//坐标
	int x;
	int y;
	//指向下一个节点的指针
	struct snakeNode* next;
}snakeNode,*psnakeNode_Hand;


//贪吃蛇 
typedef struct Snake
{
	psnakeNode_Hand Head;//指向蛇头的指针
	psnakeNode_Hand food;//指向食物的指针
	enum Direction dir;//蛇的方向
	enum Game_State state;//游戏状态
	int food_scores;//一个食物的分数
	int sum;//总分
	int sleep_time;//蛇的速度:休息时间(时间越短,速度越快; 时间越长,速度越慢)
}Snake, * psnake;


//初始化
void Game_start(psnake ps);
//定位(x,y)
void set_pos(int x, int y);

//隐藏光标+控制台大小
static void set_window();

//欢迎开始界面
static void wecometogame();

//地图打印
void Drawmap();

//创建蛇
void Create_snake(psnake ps);

//创建食物
void Create_food(psnake ps);

//==============================================


//游戏运行
void Game_run(psnake ps);

//蛇的移动
void SnakeMove(psnake ps);

//判断是否遇到食物
int Nextfood(psnakeNode_Hand pn, psnake ps);

//吃掉食物
void Eatfood(psnakeNode_Hand pn, psnake ps);

//没有食物
void Nofood(psnakeNode_Hand pn,psnake ps);


//判断撞墙
void KillByWall(psnake ps);
//判断撞到自己
void KillBySelf(psnake ps);

//==============================================

//结束游戏
void Game_end(psnake ps);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1620256.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关注四个要点,以“韧性”助力大数据局数据安全体系化建设

近日&#xff0c;中国互联网协会数字政府发展工作委员会主办的“推进政务服务提质增效的思考与实践”主题沙龙活动在京举行&#xff0c;中央、地方政府信息化部门相关负责同志及工委会18家成员单位的业界专家和团队负责人参会。 美创科技获邀参加&#xff0c;技术专家张骥带来《…

神州设备互联接口IPV6地址用本地链路地址配置路由综合运用

一、基本配置: SW-1: SW-1>ena SW-1#conf SW-1(config)#ipv6 enable SW-1(config)#vlan 100 SW-1(config)#int l1 SW-1(config-if-loopback1)#ip add 1.1.1.1 255.255.255.255 SW-1(config-if-loopback1)#ipv6 add 2001:1::1/128 SW-1(config-if-loopback1)#e…

python绘制随机地形地图

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 当我们谈论计算机编程中的地图生成时&#xff0c;通常会想到游戏开发、仿真模拟或者数据可视…

使用预训练模型构建自己的深度学习模型(迁移学习)

在深度学习的实际应用中&#xff0c;很少会去从头训练一个网络&#xff0c;尤其是当没有大量数据的时候。即便拥有大量数据&#xff0c;从头训练一个网络也很耗时&#xff0c;因为在大数据集上所构建的网络通常模型参数量很大&#xff0c;训练成本大。所以在构建深度学习应用时…

大数据学习第四天

文章目录 yaml 三大组件的方式交互流程hive 使用安装mysql(hadoop03主机)出现错误解决方式临时密码 卸载mysql (hadoop02主机)卸载mysql(hadoop01主机执行)安装hive上传文件解压解决版本差异修改hive-env.sh修改 hive-site.xml上传驱动包初始化元数据在hdfs 创建hive 存储目录启…

毫米波雷达模块在高精度人体姿态识别的应用

人体姿态识别是计算机视觉领域中的重要问题之一&#xff0c;具有广泛的应用前景&#xff0c;如智能安防、虚拟现实、医疗辅助等。毫米波雷达技术作为一种无需直接接触目标就能实现高精度探测的感知技术&#xff0c;在人体姿态识别领域具有独特的优势。本文将探讨毫米波雷达模块…

kubeadmin搭建自建k8s集群

一、安装要求 在开始之前&#xff0c;部署Kubernetes集群的虚拟机需要满足以下几个条件&#xff1a; 操作系统 CentOS7.x-86_x64硬件配置&#xff1a;2GB或更多RAM&#xff0c;2个CPU或更多CPU&#xff0c;硬盘30GB或更多【注意master需要两核】可以访问外网&#xff0c;需要…

Python 全栈体系【四阶】(三十四)

第五章 深度学习 六、PaddlePaddle 图像分类 4. 思路及实现 4.1 数据集介绍 来源&#xff1a;爬虫从百度图片搜索结果爬取 内容&#xff1a;包含 1036 张水果图片&#xff0c;共 5 个类别&#xff08;苹果 288 张、香蕉 275 张、葡萄 216 张、橙子 276 张、梨 251 张&#…

NVIDIA Jetson jtop查看资源信息

sudo -H pip install -U jetson-stats 安装好之后可能需要reboot 执行jtop&#xff1a; 时间久了可能会退出&#xff0c;可参考如下再次启动。 nvidiategra-ubuntu:~$ jtop The jtop.service is not active. Please run: sudo systemctl restart jtop.service nvidiategra-ub…

【古琴】倪诗韵古琴雷修系列(形制挺多的)

雷音系列雷修&#xff1a;“修”字取意善、美好的&#xff0c;更有“使之完美”之意。精品桐木或普通杉木制&#xff0c;栗壳色&#xff0c;纯鹿角霜生漆工艺。 方形龙池凤沼。红木配件&#xff0c;龙池上方有“倪诗韵”亲笔签名&#xff0c;凤沼下方&#xff0c;雁足上方居中位…

mPEG-Biotin,Methoxy PEG Biotin在免疫亲和层析、荧光标记和生物传感器等领域发挥关键作用

【试剂详情】 英文名称 mPEG-Biotin&#xff0c;Methoxy PEG Biotin 中文名称 聚乙二醇单甲醚生物素&#xff0c;甲氧基-聚乙二醇-生物素 外观性状 由分子量决定&#xff0c;固体或者粘稠液体。 分子量 0.4k&#xff0c;0.6k&#xff0c;1k&#xff0c;2k&#xff0c;3.…

Activiti7基础

Activiti7 一、工作流介绍 1.1 概念 工作流(Workflow)&#xff0c;就是通过计算机对业务流程自动化执行管理。它主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程&#xff0c;从而实现某个预期的业务目标&#xff0c;或者促使此目标…

2024-04-23 linux 查看内存占用情况的命令free -h和cat /proc/meminfo

一、要查看 Linux 系统中的内存占用大小&#xff0c;可以使用 free 命令或者 top 命令。下面是这两个命令的简要说明&#xff1a; 使用 free 命令&#xff1a; free -h这将显示系统当前的内存使用情况&#xff0c;包括总内存、已用内存、空闲内存以及缓冲区和缓存的使用情况。…

Git笔记-配置ssh

Git在Deepin中的ssh配置 一、环境二、安装1. 查看GitHub账户2. 配置 git3. 生成 ssh key 三、配置 一、环境 系统&#xff1a; Deepin v23 Git仓库&#xff1a;GitHub 二、安装 1. 查看GitHub账户 在设置界面看到自己的邮箱&#xff0c;这个邮箱就是后面会用到的邮箱 2. …

上位机图像处理和嵌入式模块部署(树莓派4b的一种固件部署方法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 如果软件开发好了之后&#xff0c;下面就是实施和部署。对于树莓派4b来说&#xff0c;部署其实就是烧录卡和拷贝文件。之前我们烧录卡&#xff0c;…

Jenkins CI/CD 持续集成专题四 Jenkins服务器IP更换

一、查看brew 的 services brew services list 二、编辑 homebrew.mxcl.jenkins-lts.plist 将下面的httpListenAddress值修改为自己的ip 服务器&#xff0c;这里我是用的本机的ip 三 、重新启动 jenkins-lts brew services restart jenkins-lts 四 浏览器访问 http://10.85…

26版SPSS操作教程(高级教程第十三章)

前言 #今日世界读书日&#xff0c;宝子你&#xff0c;读书了嘛~ #本期内容&#xff1a;主成分分析、因子分析、多维偏好分析 #由于导师最近布置了学习SPSS这款软件的任务&#xff0c;因此想来平台和大家一起交流下学习经验&#xff0c;这期推送内容接上一次高级教程第十二章…

卓越体验的秘密武器:评测ToDesk云电脑、青椒云、天翼云的稳定性和流畅度

大家好&#xff0c;我是猫头虎。近两年随着大模型的火爆&#xff0c;我们本地环境常常难以满足运行这些大模型的硬件需求。因此&#xff0c;云电脑平台成为了一个理想的解决方案。今天&#xff0c;我将介绍并评测几款主流云电脑产品&#xff1a;ToDesk云电脑、天翼云电脑和青椒…

网络通信安全

一、网络通信安全基础 TCP/IP协议简介 TCP/IP体系结构、以太网、Internet地址、端口 TCP/IP协议简介如下&#xff1a;&#xff08;from文心一言&#xff09; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff0c;传输控制协议/网际协议&#xff0…

PVE虚拟机隐藏状态栏虚拟设备

虚拟机启动后&#xff0c;状态栏会出现一些虚拟设备&#xff0c;点击弹出会导致虚拟机无法使用。 解决方案&#xff1a; 1、在桌面新建disable_virtio_removale.bat文件&#xff0c;内容如下&#xff1a; ECHO OFF FOR /f %%A IN (reg query "HKLM\SYSTEM\CurrentContro…