C语言实现贪吃蛇游戏(全干货免费)

目录

1、实现环境、思路

2、win32 API 函数

2.1 控制台程序

2.2 GetStdHandle

2.3 GetConsoleCursorInfo

2.4 CONSOLE_CURSOR_INFO

2.5 SetConsoleCursorInfo

2.6 控制台屏幕上坐标COORD

2.7 SetConsoleCursorPosition

3、贪吃蛇游戏设计与分析

3.1 GameStart 初始化游戏

3.1.1  welComeToGame 打印欢迎界面

3.1.2 CreateMap 地图

3.1.3 InitSnake 初始化贪吃蛇

3.3.4 CreatFood 创建食物

3.2 GameRun 运行游戏

 3.2.1 PrintHelpInfo 打印规则和分数

3.2.2 Pause 暂停功能

3.2.3  SnakeMove 蛇的移动

3.2.4 NextIsFood 判断走的下一个节点是否是食物

3.2.5 EatFood 是食物就吃掉

3.2.6 NoFood 不是食物也吃,然后释放尾节点

3.2.7 KillByWall 撞墙

3.2.8 KillBySelf 自杀(吃到自身)

3.3 GameEnd 结束游戏

4、 完整代码实现,分3个文件,复制粘贴即可运行

4.1 Snake.h

4.2 Snake.c

4.3 test.c


1、实现环境、思路

使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇实现基本的功能:

• 贪吃蛇地图绘制
• 蛇吃食物的功能 (上、下、左、右方向键控制蛇的动作)
• 蛇撞墙死亡
• 蛇撞自身死亡
• 计算得分
• 蛇身加速、减速
• 暂停游戏

技术要点:C语言、数据结构(链表)、枚举、结构体、动态内存管理、预处理指令、win32 API

2、win32 API 函数

Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外, 它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程式达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用(Application), 所以便 称之为 Application Programming Interface,简称 API 函数 。WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口。

2.1 控制台程序

在Windows中win+R输入cmd即可唤起控制台程序,使用cmd命令可以设置控制台窗口长宽。

mode con cols=120 lines=40

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

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

HANDLE GetStdHandle(DWORD nStdHandle);

就单对这个贪吃蛇的项目来说,我们需要在windows的控制台上执行的,也就是我们需要找到这个控制台,取得这个控制台的句柄,建立联系。

实例:

HANDLE hOutput = NULL;

//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

2.3 GetConsoleCursorInfo

检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息

BOOL WINAPI GetConsoleCursorInfo(
 HANDLE hConsoleOutput,   //句柄
 PCONSOLE_CURSOR_INFO lpConsoleCursorInfo  //一个结构体接收有关控制台游标的信息
);

实例

HANDLE hOutput = NULL;
//获取标准输出的句柄(用来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台的句柄

CONSOLE_CURSOR_INFO CursorInfo; 
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

2.4 CONSOLE_CURSOR_INFO

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

typedef struct _CONSOLE_CURSOR_INFO {
 DWORD dwSize;
 BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

• dwSize,由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。

• bVisible,游标的可见性。 如果光标可见,则此成员为 TRUE。

例如我们要隐藏闪烁的光标:

CursorInfo.bVisible 1 = false; //隐藏控制台光标

2.5 SetConsoleCursorInfo

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

BOOL WINAPI SetConsoleCursorInfo(
 HANDLE hConsoleOutput, //句柄
 const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo  //所需设置光标信息的结构体指针
);

实例:

HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态

2.6 控制台屏幕上坐标COORD

COORD 是Windows API中定义的一种结构,表示一个字符在控制台屏幕上的坐标
typedef struct _COORD {
 SHORT X;
 SHORT Y;
} COORD, *PCOORD;
给坐标赋值:
COORD pos={ 10 , 15 };

2.7 SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。

BOOL WINAPI SetConsoleCursorPosition(
 HANDLE hConsoleOutput,
 COORD pos
);

实例:

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

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

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

2.8 GetAsyncKeyState

获取按键情况,GetAsyncKeyState的函数原型如下:

SHORT GetAsyncKeyState(
 int vKey
);

GetAsyncKeyState 的返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。

如果我们要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.

define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

3、贪吃蛇游戏设计与分析

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

typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next; //链接每一个节点组成的一条蛇
}SnakeNode, * pSnakeNode;

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

typedef struct Snake {
	pSnakeNode _psnake;//贪吃蛇的第一个节点,头结点的指针
	pSnakeNode _pFood; //指向食物节点的指针
	int _Score;//贪吃蛇累计的总分
	int _FootWeight;//一个食物的分数
	int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长。速度越慢
	enum DIRECTION _Dir;//描述蛇的方向
	enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;

 以下是枚举蛇方向和存活状态:

enum DIRECTION
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//贪吃蛇的存活的状态
enum GAME_STATUS {
	OK,//正常运行
	END_NORMAL,//按esc退出
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF//自杀
};

我们设计这个贪吃蛇分三个步骤,初始化贪吃蛇(包括地图的创建以及贪吃蛇本身的结构)、运行游戏的过程、结束游戏(释放资源),接下来就以这些步骤开始:

3.1 GameStart 初始化游戏

游戏的初始化

//游戏初始化
void GameStart(pSnake ps) {
	setlocale(LC_ALL, "");
	system("mode con cols=120 lines=40"); //设置了x=120,y=40控制台窗口
	system("title 贪吃蛇");

	//1_隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //先获得控制台的句柄
	//一个控制台光标的信息的结构体 CONSOLE_CURSOR_INFO
	CONSOLE_CURSOR_INFO CursorInfo;
	//通过句柄获取控制台的光标信息,并储存在 CursorInfo 内
	GetConsoleCursorInfo(handle, &CursorInfo);
	//通过修改 CursorInfo 内的光标信息 bVisible 隐藏掉,不显示
	CursorInfo.bVisible = false;  //需要头文件 <stdbool.h>
	//修改成功后需要执行修改
	SetConsoleCursorInfo(handle, &CursorInfo);

	//2_打印欢迎界面
	welComeToGame();

	//3_创建地图
	CreateMap();

	//4_初始化贪吃蛇
	InitSnake(ps);

	//5_创建食物
	CreatFood(ps);
}
3.1.1  welComeToGame 打印欢迎界面
void welComeToGame() {
	SetPos(51, 18);  //定位控制台光标的位置
	printf("欢迎来到贪吃蛇游戏");

	SetPos(53, 36);
	//Windows 中暂停的指令,按任意键结束暂停
	system("pause");

	//清除之前屏幕上打印的信息
	system("cls");

	SetPos(51, 18);
	printf("游戏玩法规则如下:");

	SetPos(35, 20);
	printf("←↑→↓来操控贪吃蛇移动,Esc退出游戏,Space暂停游戏");

	SetPos(53, 22);
	printf("F3加速,F4减速");

	SetPos(53, 36);
	system("pause");

	system("cls");
}

3.1.2 CreateMap 地图
void CreateMap() {
	//上
	SetPos(0, 0);
	for (int i = 0; i <= 78; i += 2) {
		wprintf(L"%lc", WALL);
	}
	//左
	for (int i = 1; i <= 34; i++) {
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (int i = 1; i <= 34; i++) {
		SetPos(78, i);
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 34);
	for (int i = 0; i <= 78; i += 2) {
		wprintf(L"%lc", WALL);
	}
}
3.1.3 InitSnake 初始化贪吃蛇
void InitSnake(pSnake ps) {
	pSnakeNode cur = NULL;
	for (int i = 0; i < 5; i++) {
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL) {
			perror("InitSnake() error!");
			return;
		}
		cur->x = POS_X + i * 2;
		cur->y = POS_Y;
		cur->next = NULL;

		//头插法
		if (ps->_psnake == NULL) {
			ps->_psnake = cur;
		}
		else {
			cur->next = ps->_psnake;
			ps->_psnake = cur;
		}
	}
	// 遍历这个链表
	while (cur) {
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	
	ps->_Status = OK;
	ps->_Score = 50;
	ps->_pFood = NULL;
	ps->_SleepTime = 200;
	ps->_FootWeight = 10;
	ps->_Dir = RIGHT;
}
3.3.4 CreatFood 创建食物
void CreatFood(pSnake ps) {
	//1、坐标应该是随机生成的,但是这个随机是由约束的
	//2、食物坐标必须在方框内
	//3、食物的x坐标必须是2的倍数
	int x = 0;
	int y = 0;
again:
	do {
		x = rand() % 76 + 2;
		y = rand() % 33 + 1;
	} while (x % 2 != 0);

	//4、食物的坐标不能跟蛇身重叠,冲突 ,一一进行比较
	pSnakeNode cur = ps->_psnake;
	while (cur) {
		//比较
		if (cur->x == x && cur->y == y) {
			//出现重叠的就 goto 前面重新来过
			goto again;
		}
		cur = cur->next;
	}
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL) {
		perror("CreateFood(); malloc error!");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	ps->_pFood = pFood;

	SetPos(x, y);
	wprintf(L"%lc", FOOD);
}

3.2 GameRun 运行游戏

游戏运行期间,右侧打印帮助信息,提示玩家根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。确定了蛇的方向和速度,蛇就可以移动了。

void GameRun(pSnake ps) {
	PrintHelpInfo(); //打印游戏规则介绍和分数
	do {
		SetPos(85, 10);
		printf("得分:%05d", ps->_Score);
		SetPos(85, 11);
		printf("食物分数:%2d", ps->_FootWeight);

		//检测按键情况 
		//本来方向就是向上就不能往相反方向了
		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 = END_NORMAL;
			break;
		}
		else if (KEY_PRESS(VK_SPACE)) {
			//封装一个暂停函数
			Pause();
		}
		//加速
		else if (KEY_PRESS(VK_F3)) {
			if (ps->_SleepTime >= 100) {
				ps->_SleepTime -= 30;
				ps->_FootWeight += 2;
			}
		}
		//减速
		else if (KEY_PRESS(VK_F4)) {
			if (ps->_SleepTime <= 320) {
				ps->_SleepTime += 30;
				ps->_FootWeight -= 2;
			}
		}
		//设置完后要应用休眠的时间
		Sleep(ps->_SleepTime);
		//让蛇移动起来
		SnakeMove(ps);

	} while (ps->_Status == OK);
}
 3.2.1 PrintHelpInfo 打印规则和分数
void PrintHelpInfo() {

	SetPos(85, 12);
	printf("1、不能撞墙,不能吃到自己");

	SetPos(85, 14);
	printf("2、←↑→↓来操控贪吃蛇移动");

	SetPos(85, 16);
	printf("3、F3加速,F4减速");

	SetPos(85, 18);
	printf("4、Esc退出游戏,Space暂停游戏");

}
3.2.2 Pause 暂停功能
void Pause() {
    //让系统一直休眠,直到再次按下空格键
	while (1) {
		Sleep(200);
		if (KEY_PRESS(VK_SPACE)) {
			break;
		}
	}
}
3.2.3  SnakeMove 蛇的移动

先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。确定了下一个位置后,看下一个位置是否是食物(NextIsFood),是食物就做吃食物处理(EatFood),如果不是食物则做前进一步的处理(NoFood)。蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。

void SnakeMove(pSnake ps) {
	//为下一个移动的点创建一个节点
	pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNext == NULL) {
		perror("SnakeMove() malloc() error!");
		return;
	}

	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 (NextIsFood(ps, pNext)) {
		//吃掉食物
		EatFood(ps, pNext);
	}
	else {
		//不吃
		NoFood(ps, pNext);
	}

	判断是否撞墙
	KillByWall(ps);
	判断是否自杀
	KillBySelf(ps);
}
3.2.4 NextIsFood 判断走的下一个节点是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext) {
	if (ps->_pFood->x == pNext->x && ps->_pFood->y == pNext->y) {
		return 1;
	}
	return 0;
}
3.2.5 EatFood 是食物就吃掉
void EatFood(pSnake ps, pSnakeNode pNext) {
	pNext->next = ps->_psnake;
	ps->_psnake = pNext;


	pSnakeNode cur = ps->_psnake;
	while (cur) {
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//给总分加上分数
	ps->_Score += ps->_FootWeight;

	free(ps->_pFood);
	//还得重新创建新的食物
	CreatFood(ps);
}
3.2.6 NoFood 不是食物也吃,然后释放尾节点
void NoFood(pSnake ps, pSnakeNode pNext) {
	//头插入节点
	pNext->next = ps->_psnake;
	ps->_psnake = pNext;

	//释放尾节点
	pSnakeNode cur = ps->_psnake;
	while (cur->next->next != NULL) {
		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;
}
3.2.7 KillByWall 撞墙
void KillByWall(pSnake ps) {
	if (ps->_psnake->x == 0 || ps->_psnake->y == 35 || ps->_psnake->x == 80 || ps->_psnake->y == 0) {
		ps->_Status = KILL_BY_WALL;
	}
}
3.2.8 KillBySelf 自杀(吃到自身)
void KillBySelf(pSnake ps) {
	pSnakeNode cur = ps->_psnake->next;
	while (cur) {
		if (ps->_psnake->x == cur->x && ps->_psnake->y == cur->y) {
			ps->_Status = KILL_BY_SELF;
			
		}
		cur = cur->next;
	}
}

3.3 GameEnd 结束游戏

void GameEnd(pSnake ps) {
	SetPos(39, 16);
	switch (ps->_Status) {
	case END_NORMAL:
		printf("退出游戏成功!(っ °Д °;)っ下次还要再来一把");
		break;
	case KILL_BY_WALL:
		printf("撞墙啦!(╯°□°)╯︵ ┻━┻");
		break;
	case KILL_BY_SELF:
		printf("自杀啦!你怎么咬到自己了啊!( ´・・)ノ(._.`)");
		break;
	}

	SetPos(39, 18);

	pSnakeNode cur = ps->_psnake;
	while (cur) {
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

4、 完整代码实现,分3个文件,复制粘贴即可运行

4.1 Snake.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<locale.h>
#include<stdbool.h>
#include<windows.h>
#include<time.h>

//检测按键定义的宏
#define KEY_PRESS(VK) (GetAsyncKeyState(VK)& 0x1 ? 1:0)
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'


#define POS_X 24
#define POS_Y 5

//方向
enum DIRECTION
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//贪吃蛇的存活的状态
enum GAME_STATUS {
	OK,//正常运行
	END_NORMAL,//按esc退出
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF//自杀
};


//贪吃蛇本身的节点
typedef struct SnakeNode {
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode, * pSnakeNode; //相当于 typedef struct SnakeNode* pSnakeNode;

//描述这条贪吃蛇结构
typedef struct Snake {
	pSnakeNode _psnake;//贪吃蛇的第一个节点,头结点的指针
	pSnakeNode _pFood; //指向食物节点的指针
	int _Score;//贪吃蛇累计的总分
	int _FootWeight;//一个食物的分数
	int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长。速度越慢
	enum DIRECTION _Dir;//描述蛇的方向
	enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;

//定位控制台光标
void SetPos(int x, int y);


//游戏初始化
void GameStart(pSnake ps);

//2_打印欢迎界面
void welComeToGame();

//3_创建地图
void CreateMap();

//4_初始化贪吃蛇
void InitSnake(pSnake ps);


//————————————————————————————————————————————————————————
//运行过程
void GameRun(pSnake ps);

//打印基本信息
void PrintHelpInfo();

//暂停
void Pause();

//蛇移动
void SnakeMove(pSnake ps);

//判断是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext);

//吃掉食物
void EatFood(pSnake ps, pSnakeNode pNext);

//不吃食物
void NoFood(pSnake ps, pSnakeNode pNext);

//判断是否撞墙
void KillByWall(pSnake ps);

//判断是否自杀
void KillBySelf(pSnake ps);


//————————————————————————————————————————————————————————————
//游戏结束
void GameEnd(pSnake ps);

4.2 Snake.c

#include"Snake.h"

void SetPos(int x, int y) {
	//获取控制台句柄
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	//COORD是控制台存储一个坐标的结构体,
	COORD pos = { x,y };
	//设置光标的所在位置 SetConsoleCursorPosition
	SetConsoleCursorPosition(handle, pos);
}


//打印欢迎界面
void welComeToGame() {
	SetPos(51, 18);
	printf("欢迎来到贪吃蛇游戏");

	SetPos(53, 36);
	//Windows 中暂停的指令,按任意键结束暂停
	system("pause");

	//清除之前屏幕上打印的信息
	system("cls");

	SetPos(51, 18);
	printf("游戏玩法规则如下:");

	SetPos(35, 20);
	printf("←↑→↓来操控贪吃蛇移动,Esc退出游戏,Space暂停游戏");

	SetPos(53, 22);
	printf("F3加速,F4减速");

	SetPos(53, 36);
	system("pause");

	system("cls");
}

//游戏地图
void CreateMap() {
	//上
	SetPos(0, 0);
	for (int i = 0; i <= 78; i += 2) {
		wprintf(L"%lc", WALL);
	}
	//左
	for (int i = 1; i <= 34; i++) {
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (int i = 1; i <= 34; i++) {
		SetPos(78, i);
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 34);
	for (int i = 0; i <= 78; i += 2) {
		wprintf(L"%lc", WALL);
	}
}

//初始化贪吃蛇
void InitSnake(pSnake ps) {
	pSnakeNode cur = NULL;
	for (int i = 0; i < 5; i++) {
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL) {
			perror("InitSnake() error!");
			return;
		}
		cur->x = POS_X + i * 2;
		cur->y = POS_Y;
		cur->next = NULL;

		//头插法
		if (ps->_psnake == NULL) {
			ps->_psnake = cur;
		}
		else {
			cur->next = ps->_psnake;
			ps->_psnake = cur;
		}
	}
	// 遍历这个链表
	while (cur) {
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	
	ps->_Status = OK;
	ps->_Score = 50;
	ps->_pFood = NULL;
	ps->_SleepTime = 200;
	ps->_FootWeight = 10;
	ps->_Dir = RIGHT;
}

//创建食物
void CreatFood(pSnake ps) {
	//1、坐标应该是随机生成的,但是这个随机是由约束的
	//2、食物坐标必须在方框内
	//3、食物的x坐标必须是2的倍数
	int x = 0;
	int y = 0;
again:
	do {
		x = rand() % 76 + 2;
		y = rand() % 33 + 1;
	} while (x % 2 != 0);

	//4、食物的坐标不能跟蛇身重叠,冲突 ,一一进行比较
	pSnakeNode cur = ps->_psnake;
	while (cur) {
		//比较
		if (cur->x == x && cur->y == y) {
			//出现重叠的就 goto 前面重新来过
			goto again;
		}
		cur = cur->next;
	}
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL) {
		perror("CreateFood(); malloc error!");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	ps->_pFood = pFood;

	SetPos(x, y);
	wprintf(L"%lc", FOOD);

}

//游戏初始化
void GameStart(pSnake ps) {
	setlocale(LC_ALL, "");
	system("mode con cols=120 lines=40");
	system("title 贪吃蛇");
	//1_隐藏光标

	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //先获得控制台的句柄
	//一个控制台光标的信息的结构体 CONSOLE_CURSOR_INFO
	CONSOLE_CURSOR_INFO CursorInfo;
	//通过句柄获取控制台的光标信息,并储存在 CursorInfo 内
	GetConsoleCursorInfo(handle, &CursorInfo);
	//通过修改 CursorInfo 内的光标信息 bVisible 隐藏掉,不显示
	CursorInfo.bVisible = false;  //需要头文件 <stdbool.h>
	//修改成功后需要执行修改
	SetConsoleCursorInfo(handle, &CursorInfo);

	//2_打印欢迎界面
	welComeToGame();

	//3_创建地图
	CreateMap();

	//4_初始化贪吃蛇
	InitSnake(ps);

	//5_创建食物
	CreatFood(ps);
}

//—————————以下是运行过程——————————————————————————————————————————————————
//打印基本信息
void PrintHelpInfo() {

	SetPos(85, 12);
	printf("1、不能撞墙,不能吃到自己");

	SetPos(85, 14);
	printf("2、←↑→↓来操控贪吃蛇移动");

	SetPos(85, 16);
	printf("3、F3加速,F4减速");

	SetPos(85, 18);
	printf("4、Esc退出游戏,Space暂停游戏");

}

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

//判断是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext) {
	if (ps->_pFood->x == pNext->x && ps->_pFood->y == pNext->y) {
		return 1;
	}
	return 0;
}

//吃掉食物
void EatFood(pSnake ps, pSnakeNode pNext) {
	pNext->next = ps->_psnake;
	ps->_psnake = pNext;


	pSnakeNode cur = ps->_psnake;
	while (cur) {
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//给总分加上分数
	ps->_Score += ps->_FootWeight;

	free(ps->_pFood);
	//还得重新创建新的食物
	CreatFood(ps);
}

//不吃食物
void NoFood(pSnake ps, pSnakeNode pNext) {
	//头插入节点
	pNext->next = ps->_psnake;
	ps->_psnake = pNext;

	//释放尾节点
	pSnakeNode cur = ps->_psnake;
	while (cur->next->next != NULL) {
		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 KillByWall(pSnake ps) {
	if (ps->_psnake->x == 0 || ps->_psnake->y == 35 || ps->_psnake->x == 80 || ps->_psnake->y == 0) {
		ps->_Status = KILL_BY_WALL;
	}
}

//判断是否自杀
void KillBySelf(pSnake ps) {
	pSnakeNode cur = ps->_psnake->next;
	while (cur) {
		if (ps->_psnake->x == cur->x && ps->_psnake->y == cur->y) {
			ps->_Status = KILL_BY_SELF;
			
		}
		cur = cur->next;
	}
}

//蛇移动
void SnakeMove(pSnake ps) {
	//为下一个移动的点创建一个节点
	pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNext == NULL) {
		perror("SnakeMove() malloc() error!");
		return;
	}

	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 (NextIsFood(ps, pNext)) {
		//吃掉食物
		EatFood(ps, pNext);
	}
	else {
		//不吃
		NoFood(ps, pNext);
	}

	判断是否撞墙
	KillByWall(ps);
	判断是否自杀
	KillBySelf(ps);
}

void GameRun(pSnake ps) {
	PrintHelpInfo();
	do {
		SetPos(85, 10);
		printf("得分:%05d", ps->_Score);
		SetPos(85, 11);
		printf("食物分数:%2d", ps->_FootWeight);

		//检测按键情况 
		//本来方向就是向上就不能往相反方向了
		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 = END_NORMAL;
			break;
		}
		else if (KEY_PRESS(VK_SPACE)) {
			//封装一个暂停函数
			Pause();
		}
		//加速
		else if (KEY_PRESS(VK_F3)) {
			if (ps->_SleepTime >= 100) {
				ps->_SleepTime -= 30;
				ps->_FootWeight += 2;
			}
		}
		//减速
		else if (KEY_PRESS(VK_F4)) {
			if (ps->_SleepTime <= 320) {
				ps->_SleepTime += 30;
				ps->_FootWeight -= 2;
			}
		}
		//设置完后要应用休眠的时间
		Sleep(ps->_SleepTime);
		//让蛇移动起来
		SnakeMove(ps);

	} while (ps->_Status == OK);
}
//————————游戏善后———————————————————————————————————————————————————


//游戏结束
void GameEnd(pSnake ps) {
	SetPos(39, 16);
	switch (ps->_Status) {
	case END_NORMAL:
		printf("退出游戏成功!(っ °Д °;)っ下次还要再来一把");
		break;
	case KILL_BY_WALL:
		printf("撞墙啦!(╯°□°)╯︵ ┻━┻");
		break;
	case KILL_BY_SELF:
		printf("自杀啦!你怎么咬到自己了啊!( ´・・)ノ(._.`)");
		break;
	}

	SetPos(39, 18);

	pSnakeNode cur = ps->_psnake;
	while (cur) {
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

4.3 test.c

#include"Snake.h"

void test() {
	int ch = 0;
	do {
		Snake snake = { 0 };//创建贪吃蛇
		//初始化状态界面
		//1、游戏开始 - 初始化游戏
		GameStart(&snake);
		//2、游戏运行 - 游戏的正常运行过程
		GameRun(&snake);
		//3、游戏结束 - 游戏善后(释放资源)
		GameEnd(&snake);
		printf("是否再来一把(Y/N)");
		ch = getchar();
		getchar();//清理
	} while (ch == 'Y' || ch == 'y');

}

int main()
{
	setlocale(LC_ALL, ""); //使用本地的ASCII环境
	srand((unsigned int)time(NULL)); //随机数的设置
	test();

	return 0;
}

  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
用windows api 做的贪吃蛇 #include #include"resource.h" #include"Node.h" #include #include TCHAR szAppname[] = TEXT("Snack_eat"); #define SIDE (x_Client/80) #define x_Client 800 #define y_Client 800 #define X_MAX 800-20-SIDE //点x的范围 #define Y_MAX 800-60-SIDE //点y的范围 #define TIME_ID 1 #define SECOND 100 #define NUM_POINT 10 //点的总个数 #define ADD_SCORE 10 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd; //窗口句柄 MSG msg; //消息 WNDCLASS wndclass; //窗口类 HACCEL hAccel;//加速键句柄 wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口的水平和垂直尺寸被改变时,窗口被重绘 wndclass.lpfnWndProc = WndProc; //窗口过程为WndProc函数 wndclass.cbClsExtra = 0; //预留额外空间 wndclass.cbWndExtra = 0; //预留额外空间 wndclass.hInstance = hInstance; //应用程序的实例句柄,WinMain的第一个参数 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //设置图标 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //载入预定义的鼠标指针 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //设置画刷 wndclass.lpszMenuName = szAppname; //设置菜单 wndclass.lpszClassName = szAppname; //设置窗口类的名字 if (!RegisterClass(&wndclass))//注册窗口类 { MessageBox(NULL, TEXT("这个程序需要windows NT!"), szAppname, MB_ICONERROR); return 0; } hwnd = CreateWindow(szAppname, TEXT("Snack_eat"),//CreateWindow函数调用时,WndProc将受到WM_CREATE WS_OVERLAPPEDWINDOW&~WS_THICKFRAME& ~WS_MAXIMIZEBOX,//普通的层叠窗口&禁止改变大小&禁止最大化 CW_USEDEFAULT, //初始x坐标(默认) CW_USEDEFAULT, //初始y坐标 x_Client, //初始x方向尺寸 770 y_Client, //初始y方向尺寸 750 NULL, //父窗口句柄 NULL, //窗口菜单句柄 hInstance, //程序实例句柄 WinMain函数中第二个参数 NULL); //创建参数 ShowWindow(hwnd, iCmdShow);//显示窗口,iCmdShow是WinMain的第四个参数,决定窗口在屏幕中的初始化显示形式,例:SW_SHOWNORMAL表示正常显示 UpdateWindow(hwnd);//使窗口客户区重绘,通过向WndProc发送一条WM_PAINT消息而完成的 hAccel = LoadAccelerators(hInstance, szAppname);//加载加速键 while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(hwnd, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }/* while (GetMessage(&msg, NULL, 0, 0))//GetMessage函数从消息队列中得到消息,填充msg。如果msg.message等于WM_QUIT,返回0,否则返回非0 { TranslateMessage(&msg);//将msg返回给windows已进行某些键盘消息的转换 DispatchMessage(&msg);//将msg再次返回给windows }*/ return msg.wParam;//msg.wParam是PostQuitMessage函数的参数值,通常是0 } ...

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值