C语言从零实现贪吃蛇小游戏

制作不易,点赞关注一下呗!!!

文章目录

  • 前言
  • 一. 技术要点
  • 二、WIN32API介绍
  • 三、贪吃蛇游戏设计与分析
  • 1.游戏开始前的初始化
  • 2.游戏运行的逻辑
  • 总结

前言

当我们掌握链表这样的数据结构之后,我们就可以用它来做一些小项目,比如童年小游戏贪吃蛇

目标:在Windows环境中控制台上使用C语言模拟实现经典小游戏贪吃蛇


一.技术要点

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

二.Win32API介绍

1.什么是Win32API

Win32 API是微软的操作系统Windows提供给开发人员的编程接口,它决定了我们开发的Windows应用程序的能力。

使用Win32 API,应用程序可以充分挖掘Windows的32位操作系统的潜力。 Mircrosoft的所有32位平台都支持统一的API,包括函数、结构、消息及接口。使用 Win32 API不但可以开发出在各种平台上都能成功运行的应用程序,而且也可以充分利用每个平台特有的功能和属性。

2.控制台程序(Console)

我们平时运行起来出现的黑框框就是控制台。

我们可以通过mode命令来设置窗口大小。

例如:设置为30行,100列

mode con cols=100 lines=30

在VS上我们可以借助system函数(头文件<windows.h>)来完成这样的命令

我们也可以通过title命令来修改控制台窗口的名称

title 贪吃蛇

3.控制台屏幕上的坐标 COORD

COORD是Win32API中定义的一个结构体,表示一个字符在屏幕缓冲区上的坐标,坐标为(0,0)的原点位于缓冲区的顶部最左侧单元格

typedef struct _COORD {

SHORT X; // horizontal coordinate

SHORT Y; // vertical coordinate

} COORD;

4.GetStdHandle

GetStdHandle 是Win32API的一个函数,它用于从特定的标准设备(标准输入,标准输出,标准错误)中获取一个句柄(用于标识不同设备的数值),使用这个句柄可以操作设备。

语法

HANDLE GetStdHandle( DWORDnStdHandle );

参数有三种取值

|-------------------|---------|
| STD_INPUT_HANDLE | 标准输入的句柄 |
| STD_OUTPUT_HANDLE | 标准输出的句柄 |
| STD_ERROR_HANDLE | 标准错误的句柄 |

实例:

5.GetConsoleCursorInfo

功能:检索有关指定控制台光标的大小和可见性

BOOL WINAPI GetConsoleCursorInfo(

HANDLE hConsoleOutput,

PCONSOLE_CURSOR_INFO lpConsoleCursorInfo

);

PCONSOLE_CURSOR_INFO是指向 CONSOLE_CURSOR_INFO这个结构体的指针,该结构接收主机光标的信息。

6.SetConsoleCursorInfo

功能:设置指定控制台屏幕缓冲区的光标大小和可见性

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

一般都是配合 GetConsoleCursorInfo函数使用,先用GetConsoleCursorInfo函数获取当前光标信息,再自己手动修改光标信息,最后用SetConsoleCursorInfo 函数设置进去!!!

**7.**GetAsyncKeyState

GetAsyncKeyState是一个用来判断函数调用时指定虚拟键的状态,确定用户当前是否按下了键盘上的一个键的函数。

返回类型为short类型,如果返回的16位short类型数据中最高位为1,则表明该键被按下,最高位为0则表示该键抬起,如果最低位为1表示该键被按过,否则为0.

|------------------|-----------|---------------------------------------------------------------------------|
| 函数名 | 返回值类型 | 备注 |
| GetAsyncKeyState | SHORT | 用来判断函数调用时指定虚拟键的状态 |
| 参数 | 类型 | 说明 |
| vKey | int | 欲测试的虚拟键的键码 |

每个键都对应一个虚拟键值,可以自行查找。

8.SetConsoleCursorPosition

有时我们可能不想让字符打印从(0,0)坐标处开始打印,这时就需要用这个函数来设置坐标

使用这个函数需要两个参数:第一个参数类型为HANDLE ,第二个参数类型为COORD

实例:

三.贪吃蛇游戏设计与分析

1.游戏开始前的初始化

数据结构的设计:

cpp 复制代码
enum GameStatus
{
	OK,
	ESC,
	KILL_BYWALL,
	KILL_BYSELF,
};
enum DIRECTION
{
	LEFT,
	RIGHT,
	UP,
	DOWN,
};
//蛇身节点
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

//整条蛇
typedef struct Snake
{
	pSnakeNode pSnake;
	pSnakeNode pFood;//指向食物的指针
	int Score;//当前累计分数
	int FoodWeight;//一个食物的分数
	int SleepTime;//休眠时间,时间越短,速度越快
	enum GameStatus Status;
	enum DIRECTION dir;
}Snake,*pSnake;

接下来打印欢迎信息 ,这里我们单独分装两个函数,一个用来设置光标位置,另一个用来打印欢迎信息:

下一步:创建地图

cpp 复制代码
 #define WALL L'□'
void CreateMap()
{
	//上
	SetPos(0, 0);
	int i = 0;
	for (i = 0; i < 69; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0,34);
	for (i = 0; i < 69; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 0; i < 34; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 0; i < 34; i++)
	{
		SetPos(68, i);
		wprintf(L"%lc", WALL);
	}
}

再下一步:初始化蛇

cpp 复制代码
void InitSnake(pSnake ps)
{
	pSnakeNode cur = NULL;
	//创建5个蛇身节点
	for (int i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("createsnake malloc");
			exit(1);
		}
		cur->x = 32 + 2 * i;
		cur->y = 7;
		cur->next = NULL;
		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->dir = RIGHT;
	ps->FoodWeight = 10;
	ps->pFood = NULL;
	ps->Score = 0;
	ps->SleepTime = 200;//200毫秒
	ps->Status = OK;
}

最后:创建食物

cpp 复制代码
void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	again:
	do {
		x = rand() % 66 + 2;
		y = rand() % 33 + 1;
	} while (x % 2 != 0);
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	pSnakeNode food = (pSnakeNode)malloc(sizeof(pSnake));
	if (!food)
	{
		perror("createfood malloc ");
		exit(1);
	}
	food->x = x;
	food->y = y;
	ps->pFood = food;
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
}

最后将这些函数放在GameStart函数中,一起完成游戏开始前的工作

2.游戏运行的逻辑

第一步:打印帮助信息

第二步:循环检测按键状态

3.游戏结束的善后


总结

所有代码放在这里:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<locale.h>
#include<string.h>
#include<stdbool.h>
#include<windows.h>
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define KEY_PRESS(vk)  ((GetAsyncKeyState(vk) & 0x1)? 1: 0)

enum GameStatus
{
	OK,
	ESC,
	KILL_BYWALL,
	KILL_BYSELF,
};
enum DIRECTION
{
	LEFT,
	RIGHT,
	UP,
	DOWN,
};
//蛇身节点
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

//整条蛇
typedef struct Snake
{
	pSnakeNode pSnake;
	pSnakeNode pFood;//指向食物的指针
	int Score;//当前累计分数
	int FoodWeight;//一个食物的分数
	int SleepTime;//休眠时间,时间越短,速度越快
	enum GameStatus Status;
	enum DIRECTION dir;
}Snake,*pSnake;

void SetPos(int x, int y);

void GameStart(pSnake snake);

void Welcome();

void CreateMap();

void InitSnake(pSnake ps);

void CreateFood(pSnake ps);

void printhelpinfo();

//游戏运行的逻辑
void GameRun(pSnake ps);
void pause();

void SnakeMove(pSnake ps);
int IsFood(pSnake ps, pSnakeNode next);
void EatFood(pSnake ps, pSnakeNode next);
void NotEatFood(pSnake ps,pSnakeNode next);

void KillByWall(pSnake ps);
void KillBySelf(pSnake ps);

void GameEnd(pSnake ps);

#include"snake.h"
void SetPos(int x, int y)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(handle, pos);
}
void Welcome()
{
	//打印欢迎信息
	SetPos(48, 15);
	printf("欢迎来到贪吃蛇小游戏\n");
	SetPos(50,35);
	system("pause");
	system("cls");

	//打印操作信息
	SetPos(35, 20);
	printf("用↑. ↓. ←. →来控制蛇的移动,F3是加速,F4是减速\n");
	SetPos(48, 22);
	printf("加速能获得更多分数\n");
	SetPos(50, 35);
	system("pause");
	system("cls");
}

void CreateMap()
{
	//上
	SetPos(0, 0);
	int i = 0;
	for (i = 0; i < 69; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0,34);
	for (i = 0; i < 69; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 0; i < 34; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 0; i < 34; i++)
	{
		SetPos(68, i);
		wprintf(L"%lc", WALL);
	}
}

void InitSnake(pSnake ps)
{
	pSnakeNode cur = NULL;
	//创建5个蛇身节点
	for (int i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("createsnake malloc");
			exit(1);
		}
		cur->x = 32 + 2 * i;
		cur->y = 7;
		cur->next = NULL;
		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->dir = RIGHT;
	ps->FoodWeight = 10;
	ps->pFood = NULL;
	ps->Score = 0;
	ps->SleepTime = 200;//200毫秒
	ps->Status = OK;
}

void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	again:
	do {
		x = rand() % 66 + 2;
		y = rand() % 33 + 1;
	} while (x % 2 != 0);
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	pSnakeNode food = (pSnakeNode)malloc(sizeof(pSnake));
	if (!food)
	{
		perror("createfood malloc ");
		exit(1);
	}
	food->x = x;
	food->y = y;
	ps->pFood = food;
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
}

void GameStart(pSnake ps)
{
	//修改控制台信息
	system("mode con cols=120 lines=40");
	system("title 贪吃蛇");
	//隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO cursorinfo;
	GetConsoleCursorInfo(handle, &cursorinfo);
	cursorinfo.bVisible = false;
	SetConsoleCursorInfo(handle, &cursorinfo);
	//打印欢迎信息
	Welcome();
	//绘制地图
	CreateMap();
	//初始化蛇
	InitSnake(ps);
	//创建食物
	CreateFood(ps);
	 
}

void printhelpinfo()
{
	SetPos(80, 18);
	printf("不能穿墙,不能咬到自己\n");
	SetPos(80, 19);
	printf("用↑ . ↓ . ← . →来控制蛇的移动\n");
	SetPos(80, 20);
	printf("F3是加速,F4是减速\n");
}

void pause()
{
	while (1)
	{
		Sleep(100);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}


int IsFood(pSnake ps,pSnakeNode next)
{
	if (ps->pFood->x == next->x && ps->pFood->y == next->y)
	{
		return 1;//是食物
	}
	else
		return 0;
} 


void EatFood(pSnake ps, pSnakeNode next)
{
	next->next = ps->pSnake;
	ps->pSnake = next;
	//打印蛇身
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->Score += ps->FoodWeight;
	//释放旧食物
	free(ps->pFood);
	ps->pFood = NULL;
	//创建新食物
	CreateFood(ps);
}


void NotEatFood(pSnake ps, pSnakeNode next)
{
	//头插法插入
	next->next = ps->pSnake;
	ps->pSnake = next;

	//释放尾节点
	pSnakeNode cur = ps->pSnake;
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//将尾节点的位置打印成空白字符
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;

}

void SnakeMove(pSnake ps)
{
	pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pnext == NULL)
	{
		perror("snakemove malloc");
		exit(1);
	}
	pnext->next = NULL;
	switch (ps->dir)
	{
	case UP:
		pnext->x = ps->pSnake->x;
		pnext->y = ps->pSnake->y-1;
		break;
	case DOWN:
		pnext->x = ps->pSnake->x;
		pnext->y = ps->pSnake->y + 1;
		break; 
	case LEFT:
		pnext->x = ps->pSnake->x-2;
		pnext->y = ps->pSnake->y;
		break;
	case RIGHT :
		pnext->x = ps->pSnake->x+2;
		pnext->y = ps->pSnake->y;
		break;
	}

	//下一个坐标是否是食物
	if (IsFood(ps, pnext))
	{
		//是食物 
		EatFood(ps, pnext);
	}
	else
	{
		//不是食物
		NotEatFood(ps,pnext);
	}
}


void KillByWall(pSnake ps)
{
	if (ps->pSnake->x == 0 || ps->pSnake->x == 68 || ps->pSnake->y == 0 || ps->pSnake->y == 33)
	{
		ps->Status = KILL_BYWALL;
	}

}

void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->pSnake->next;
	while (cur)
	{
		if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
		{
			ps->Status = KILL_BYSELF;
			return;
		}
		cur=cur->next;
	}
}


//游戏运行的逻辑
void GameRun(pSnake ps)
{
	//打印帮助信息
	printhelpinfo();
	
	do
	{
		//打印当前分数
		SetPos(80, 13);
		printf("总分:%d\n", ps->Score);
		SetPos(80, 14);
		printf("食物的分值:%02d\n", ps->FoodWeight);
		//检测按键
		//上下左右,空格,F3,F4, ESC
		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_ESCAPE))
		{
			ps->Status = ESC;
			break;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//暂停和恢复暂停
			pause();
		}
		else if (KEY_PRESS(VK_F3))
		{
			if (ps->SleepTime>=80)
			{
				ps->SleepTime -= 30;
				ps->FoodWeight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			if (ps->FoodWeight > 2)
			{
				ps->SleepTime += 30;
				ps->FoodWeight -= 2;
			}
		}
		//睡眠
		Sleep(ps->SleepTime);

	    //走一步
		SnakeMove(ps);

		//检测撞墙
		KillByWall(ps);

		//检测是否撞到自己
		KillBySelf(ps);
	} while (ps->Status == OK);
}


void GameEnd(pSnake ps)
{
	SetPos(35, 18);
	switch (ps->Status)
	{
	case ESC:
		printf("正常退出\n");
		break;
	case KILL_BYWALL:
		printf("很遗憾,撞到墙,游戏结束\n");
		break;
	case KILL_BYSELF:
		printf("很遗憾,咬到自己了,游戏结束\n");
		break;
	}

	//释放贪吃蛇
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		pSnakeNode next = cur->next;
		free(cur);
		cur = next;
	}
	free(ps->pFood);
	ps = NULL;
}

void test()
{
	int ch = 0;
	do
	{
		Snake snake = { 0 };
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(35, 18);
		printf("再来一局吗:Y/N");
		ch=getchar();
		getchar();
	} while (ch == 'y' || ch == 'Y');
}
int main()
{
	setlocale(LC_ALL, "");
    srand((unsigned int)time(NULL));
	test();
	SetPos(55, 35);
	return 0;
}
相关推荐
互联网打工人no1几秒前
每日一题——第一百二十四题
c语言
爱吃生蚝的于勒3 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~7 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
binishuaio12 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE14 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻18 分钟前
WPF中的依赖属性
开发语言·wpf
洋24027 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙28 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点30 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
NoneCoder1 小时前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发