前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >贪吃蛇项目实践!(上)

贪吃蛇项目实践!(上)

作者头像
用户11039545
发布2024-05-04 08:36:39
980
发布2024-05-04 08:36:39
举报
文章被收录于专栏:c语言c语言

大家好,今天我带着大家从0构建起贪吃蛇项目的高楼大厦~

要实现这个游戏,我们需要实现哪些功能呢?

实现基本的功能: • 贪吃蛇地图绘制 • 蛇吃⻝物的功能(上、下、左、右⽅向键控制蛇的动作) • 蛇撞墙死亡 • 蛇撞⾃⾝死亡 • 计算得分 • 蛇⾝加速、减速 • 暂停游戏

Win32API

调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等⽬的。

控制台程序

平时我们运行起来的黑框程序其实就是控制台程序。

cmd命令来设置控制台窗⼝的⻓宽:例如设置控制台窗口的大小为30行,100列。

代码语言:javascript
复制
mode con cols=100 lines=30

也可以设置控制台窗口的名字:

代码语言:javascript
复制
title 贪吃蛇

这些能在控制台窗⼝执⾏的命令,也可以调⽤C语⾔函数system来执⾏。例如:

代码语言:javascript
复制
#include <stdio.h>
int main() 
{
   system("mode con cols=100 lines=30");
   //设置cmd窗⼝名称
   system("title 贪吃蛇");
   return 0;
}

控制台屏幕上的坐标COORD

COORD是WindowsAPI中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)。

COORD类型的声明

代码语言:javascript
复制
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;

给坐标赋值

代码语言:javascript
复制
COORD pos = { 10, 15 };

GetStdHandle

GetStdHandle是⼀个WindowsAPI函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。

代码语言:javascript
复制
HANDLE GetStdHandle(DWORD nStdHandle);

例子:

代码语言:javascript
复制
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

GetConsoleCursorInfo

检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息

代码语言:javascript
复制
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

例子:

代码语言:javascript
复制
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

CONSOLE_CURSOR_INFO

这个结构体,包含有关控制台光标的信息

代码语言:javascript
复制
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

dwSize,由光标填充的字符单元格的百分⽐。此值介于1到100之间。光标外观会变化,范围从完 全填充单元格到单元底部的⽔平线条。  bVisible,游标的可⻅性。如果光标可⻅,则此成员为TRUE。

代码语言:javascript
复制
CursorInfo.bVisible = false; //隐藏控制台光标

SetConsoleCursorInfo

设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性

代码语言:javascript
复制
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
};

例子: 

代码语言:javascript
复制
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//隐藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

SetConsoleCursorPosition 

我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。

代码语言:javascript
复制
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
};

例子:

代码语言:javascript
复制
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);

SetPos:封装⼀个设置光标位置的函数

代码语言:javascript
复制
//设置光标的坐标
void SetPos(short x, short y)
{
   COORD pos = { x, y };
   HANDLE hOutput = NULL;
   //获取标准输出的句柄(⽤来标识不同设备的数值)
   hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
   //设置标准输出上光标的位置为pos
   SetConsoleCursorPosition(hOutput, pos);
}

GetAsyncKeyState

获取按键情况

代码语言:javascript
复制
SHORT GetAsyncKeyState(
   int vKey;
}

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。

代码语言:javascript
复制
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
代码语言:javascript
复制
#include <stdio.h>
#include <windows.h>
int main()
{
   while (1)
  {
     if (KEY_PRESS(0x30))
    {
        printf("0\n");
    }
  }
}

设计地图

窗⼝的坐标如下所⽰,横向的是X轴,从左向右依次增⻓,纵向是Y轴,从上到下依次增⻓。

 C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型 wchar_t 和宽字符的输⼊和输出函数,加⼊了<locale.h>头⽂件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。

<locale.h>本地化

类项

LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。 • LC_CTYPE:影响字符处理函数的⾏为。 • LC_MONETARY:影响货币格式。 • LC_NUMERIC:影响 printf() 的数字格式。 • LC_TIME:影响时间格式 strftime() 和 wcsftime() 。 • LC_ALL:针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

setlocale函数
代码语言:javascript
复制
char* setlocale (int category, const char* locale);

C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。

比如切换为本地模式:

代码语言:javascript
复制
setlocale(LC_ALL, " ");//切换到本地环境

宽字符的打印

宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引 号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls

代码语言:javascript
复制
#include <stdio.h>
#include<locale.h>
int main() {
     setlocale(LC_ALL, "");
     wchar_t ch1 = L'●';
     wchar_t ch2 = L'⽐';
     wchar_t ch3 = L'特';
     wchar_t ch4 = L'★';
     printf("%c%c\n", 'a', 'b');
     wprintf(L"%lc\n", ch1);
     wprintf(L"%lc\n", ch2);
     wprintf(L"%lc\n", ch3);
     wprintf(L"%lc\n", ch4);
     return 0;
}

地图坐标

我们假设实现⼀个棋盘27⾏,58列的棋盘(⾏和列可以根据⾃⼰的情况修改),再围绕地图画出墙

蛇⾝和⻝物

初始化状态,假设蛇的⻓度是5,蛇⾝的每个节点是●,在固定的⼀个坐标处,⽐如(24,5)处开始出现 蛇,连续5个节点。 注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬。 关于⻝物,就是在墙体内随机⽣成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的⾝体重合,然后打印★。

数据结构设计

在游戏运⾏的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变⻓⼀节,如果我们使⽤链表存储蛇的信 息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇⾝节点在地图上的坐标就⾏, 所以蛇节点结构如下:

代码语言:javascript
复制
typedef struct SnakeNode
{
  int x;
  int y;
  struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

封装snake结构维护贪吃蛇

代码语言:javascript
复制
typedef struct Snake
{
  pSnakeNode _pSnake;//维护整条蛇的指针
  pSnakeNode _pFood;//维护⻝物的指针
  enum DIRECTION _Dir;//蛇头的⽅向,默认是向右
  enum GAME_STATUS _Status;//游戏状态
  int _Socre;//游戏当前获得分数
  int _foodWeight;//默认每个⻝物10分
  int _SleepTime;//每⾛⼀步休眠时间
}Snake, * pSnake;

蛇的方向

代码语言:javascript
复制
enum DIRECTION
{
   UP = 1,
   DOWN,
   LEFT,
   RIGHT
};

游戏状态

代码语言:javascript
复制
/游戏状态
enum GAME_STATUS
{
  OK,//正常运⾏
  KILL_BY_WALL,//撞墙
  KILL_BY_SELF,//咬到⾃⼰
  END_NOMAL//正常结束
};

游戏设计流程

核⼼逻辑实现分析

游戏主逻辑

设置程序⽀持本地模式,然后进⼊游戏的主逻辑

代码语言:javascript
复制
# define _CRT_SECURE_NO_WARNINGS 
#include <locale.h>
void test()
{
	int ch = 0;
	srand((unsigned int)time(NULL));//生成随机数
	do
	{
		Snake snake = { 0 };//创建结构体
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(20, 15);//设定光标位置
		printf("再来⼀局吗?(Y/N):");
		ch = getchar();//接收用户的输入值
		getchar();//清理屏幕
	} while (ch == 'Y');
	SetPos(0, 27);//重新设定光标位置
}
int main()
{
	//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
	setlocale(LC_ALL, "");
	//测试逻辑
	test();
	return 0;
}

游戏开始(GameStart)

这个模块完成游戏的初始化任务: • 控制台窗⼝⼤⼩的设置 • 控制台窗⼝名字的设置 • ⿏标光标的隐藏 • 打印欢迎界⾯ • 创建地图 • 初始化第蛇 • 创建第⼀个⻝物

代码语言:javascript
复制
# define _CRT_DEFINE_NO_WARNINGS 
void GameStart(pSnake ps)
{
	//设置控制台窗⼝的⼤⼩,30⾏,100列
	//mode 为DOS命令
	system("mode con cols=100 lines=30");
	//设置cmd窗⼝名称
	system("title 贪吃蛇");
	//获取标准输出的句柄(⽤来标识不同设备的数值)
	HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	//影藏光标操作
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false; //隐藏控制台光标
	SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
	//打印欢迎界⾯
	WelcomeToGame();
	//打印地图
	CreateMap();
	//初始化蛇
	InitSnake(ps);
	//创造第⼀个⻝物
	CreateFood(ps);
}
打印欢迎界⾯
代码语言:javascript
复制
void WelcomeToGame()
{
	SetPos(40, 15);
	printf("欢迎来到贪吃蛇⼩游戏");
	SetPos(40, 25);//让按任意键继续的出现的位置好看点
	system("pause");
	system("cls");
	SetPos(25, 12);
	printf("⽤ ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");
	SetPos(25, 13);
	printf("加速将能得到更⾼的分数。\n");
	SetPos(40, 25);//让按任意键继续的出现的位置好看点
	system("pause");
	system("cls");
}

创建地图

代码语言:javascript
复制
#define WALL L'□'

初始化蛇⾝

代码语言:javascript
复制
void InitSnake(pSnake ps)
{
	pSnakeNode cur = NULL;
	int i = 0;
	//创建蛇⾝节点,并初始化坐标
	//头插法
	for (i = 0; i < 5; i++)
	{
		//创建蛇⾝的节点
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake()::malloc()");
			return;
		}
		//设置坐标
		cur->next = NULL;
		cur->x = POS_X + i * 2;
		cur->y = POS_Y;
		//头插法
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = cur;
		}
		else
		{
			cur->next = ps->_pSnake;
			ps->_pSnake = cur;
		}
	}
	//打印蛇的⾝体
	cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//初始化贪吃蛇数据
	ps->_SleepTime = 200;
	ps->_Socre = 0;
	ps->_Status = OK;
	ps->_Dir = RIGHT;
	ps->_foodWeight = 10;
}

创建第⼀个⻝物

先随机⽣成⻝物的坐标 ◦ x坐标必须是2的倍数 ◦ ⻝物的坐标不能和蛇⾝每个节点的坐标重复 • 创建⻝物节点,打印⻝物

代码语言:javascript
复制
#define FOOD L'★'
代码语言:javascript
复制
void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
again:
	//产⽣的x坐标应该是2的倍数,这样才可能和蛇头坐标对⻬。
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);
	pSnakeNode cur = ps->_pSnake;//获取指向蛇头的指针
	//⻝物不能和蛇⾝冲突
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode)); //创建⻝物
	if (pFood == NULL)
	{
		perror("CreateFood::malloc()");
		return;
	else
	{
		pFood->x = x;
		pFood->y = y;
		SetPos(pFood->x, pFood->y);
		wprintf(L"%c", FOOD);
		ps->_pFood = pFood;
	}
	}

游戏运⾏

KEY_PRESS

检测按键状态,我们封装了⼀个宏

代码语言:javascript
复制
 #define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

PrintHelpInfo

代码语言:javascript
复制
void PrintHelpInfo()
{
	//打印提⽰信息
	SetPos(64, 15);
	printf("不能穿墙,不能咬到⾃⼰\n");
	SetPos(64, 16);
	printf("⽤↑.↓.←.→分别控制蛇的移动.");
	SetPos(64, 17);
	printf("F3 为加速,F4 为减速\n");
	SetPos(64, 18);
	printf("ESC :退出游戏.space:暂停游戏.");
}

蛇⾝移动(SnakeMove)

代码语言:javascript
复制
void SnakeMove(pSnake ps)
{
	//创建下⼀个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc()");
		return;
	}
	//确定下⼀个节点的坐标,下⼀个节点的坐标根据,蛇头的坐标和⽅向确定
	switch (ps->_Dir)
	{
	case UP:
	{
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y - 1;
	}
	break;
	case DOWN:
	{
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y + 1;
	}
	break;
	case LEFT:
	{
		pNextNode->x = ps->_pSnake->x - 2;
		pNextNode->y = ps->_pSnake->y;
	}
	break;
	}
	//如果下⼀个位置就是⻝物
	if (NextIsFood(pNextNode, ps))
	{
		EatFood(pNextNode, ps);
	}
	else//如果没有⻝物
	{
		NoFood(pNextNode, ps);
	}
	KillByWall(ps);
	KillBySelf(ps);
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-05-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Win32API
    • 控制台程序
      • 控制台屏幕上的坐标COORD
        • GetStdHandle
          • GetConsoleCursorInfo
            • CONSOLE_CURSOR_INFO
              • SetConsoleCursorInfo
                • SetConsoleCursorPosition 
                  • SetPos:封装⼀个设置光标位置的函数
                    • GetAsyncKeyState
                    • 设计地图
                      • <locale.h>本地化
                        • 类项
                      • 宽字符的打印
                      • 地图坐标
                      • 蛇⾝和⻝物
                      • 数据结构设计
                      • 核⼼逻辑实现分析
                        • 游戏主逻辑
                          • 游戏开始(GameStart)
                            • 打印欢迎界⾯
                        • 创建地图
                        • 初始化蛇⾝
                        • 创建第⼀个⻝物
                        • 游戏运⾏
                        • KEY_PRESS
                        • PrintHelpInfo
                        • 蛇⾝移动(SnakeMove)
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档