贪吃蛇游戏的实现

一.技术要点:

贪吃蛇需要掌握:

        c语言函数,枚举,结构体,动态内存管理,预处理指令,链表,Win32 API等

二.Win32 API

1.Win32 API简介

windows可以帮应用程序卡其视窗,描绘图案,使用周边设备,,Win32 API就是windows32位平台上的应用程序编程接口

2.控制台程序

(1).使用cmd命令设置控制台窗口的长宽

输入mode con cols=100 lines=30

贪吃蛇游戏的实现_第1张图片

(2.)可以通过命令设置控制台的窗口的名字

title xxx

贪吃蛇游戏的实现_第2张图片

(3.)C语言的system函数实现控制台的命令

#include
#include
int main()
{
    system("mode con cols=50 lines=20");
    system("title 贪吃蛇");
    system("pause");

    return 0;
}

(4.)控制台屏幕上的坐标

COORD是windows API中定义的结构体,表示一个字符在控制台屏幕缓冲区上的坐标,坐标(0,0)位于缓冲区左上角

贪吃蛇游戏的实现_第3张图片

CORRD类型声明:

typedef struct _CORRD
{
    SHORT X;
    SHORT Y;
}CORRD, *PCORRD;

给结构体定坐标:

#include 
CORRD pos = { 10,15 };

(5.)GetStdHandle获取设备

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

函数的声明:

HANDLE GetStdHandle(DWORD nStdHandle);

eg.

HANDLE hOutput = NULL;

//获取标准输出的句柄
hOutput = GetStHandle(STD_OUTPUT_HANDLE);

(6.)GetConsoleCursorInfo获取光标信息

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

函数的声明:

BOOL WINAPI GetConsoleCursorInfo(
    HANDLE               hConsoleOutput 
    PCONSOLE_CURSOR_INFO lpConsoleCursorInfo 
);

 PCONSOLE_CURSOR_INFO是指向CONSOLE_CURSOR_INFO结构的指针,该结构接受有感主机游标(光标)的信息

eg.

#include 
int main()
{
    CONSOLE_CURSOR_INFO cursor_info = { 0 };
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    GetConsoleCursorInfo(handle,&cursor_info);
    return 0;
}

CONSOLE_CURSOR_INFO是结构体,其中有两个成员,:bVisible和dwSize分别控制光标的可可见性和大小

(7.)SetConsoleCursorPosition设置光标位置

void SetPos(int x,int y)
{
    //获得设备句柄
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
    //根据句柄设置光标位置
    CORRD pos = { x,y };
    SetConsoleCursorPosition(handle,pos);
}

(8.)GetAsyncKeyState获取按键情况

想要判断一个键是否被按,可以检测GetAsyncKeyState返回值的最低为是否为1

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

eg.

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 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");
        ......
    }
}

这样可以在按下数字键是在屏幕上输出数字

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

一.地图

1.本地化

提供的函数用于控制C标准库中对于不同地区会产生不一样的行为的部分

在标准中,依赖地区的部分有以下几项:

1.数字量的格式

2.货币量的格式

3.字符集

4.日期和时间的表示形式

2.类项

1.LC_COLLATE:影响字符串比较函数strcoll()和strxfrm()

2.LC_CTYPE:影响字符处理函数的行为

3.LC_MONCTARY:影响货币格式

4.LC_NUMERIC:影响printf()的数字格式

5.LC_TIME:影响时间格式strftime()和wcsfting()

6.LC_ALL:针对所有类项修改,将以上的多有类别设置为给定的语言环境

3.setlocale函数

该函数用于修改当前的地区,可以针对一个类项,也可以针对所有类项

函数声明:

char* setlocale(int category,const char* locale);

第一个参数是修改的类项,第二个为地区

C的标准给了第二个参数2种可能取值:1."C"(正常模式)  2.""(本地模式)

在任意程序执行开始时,会隐藏执行调用setlocale(LC_ALL,"C");

第二个参数也可以传入NULL,通过传入NULL可以查询默认的本地信息

4.宽字符的打印

一个宽字符占领个字符的位置

int main()
{
    setlocale(LC_ALL,"");
    
    wchar_t ch = L'中国';

    wprintf(L"%lc",ch);

    return 0;
}

5.地图坐标

可以假设设计一个27行,58列的地图并围绕地图画出墙壁,如下

贪吃蛇游戏的实现_第4张图片

二.蛇身和食物

蛇身

可以初始化蛇身长度为5,在随机坐标出现蛇,连续五个节点代表蛇身

注意:为了防止社的一个节点的一半出现在墙体里,另一半在墙外,最好让蛇身的每一个节点的X坐标为2的倍数

食物

在墙体内随机生成一个坐标(x的坐标必须为2的倍数),同时坐标不能与蛇身重合

如图:

贪吃蛇游戏的实现_第5张图片

三.数据结构设计

一.蛇身节点

蛇身可以使用链表设计,需要存储的信息为当前节点的蛇身的坐标(x,y)和下一个节点

typedef struct snakenode
{
    int x;
    int y;//节点坐标
    struct snake* next;
}snakenode, * psnakenode;

二.贪吃蛇状态

//游戏状态
enum GAME_STATUS
{
    OK = 1,//正常进行
    ESC,//退出
    KILL_BY_WALL,//撞墙
    KILL_BY_SELF//撞到自己
};

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

typedef struct snake
{
    psnakenode psnake;//维护整条蛇的指针
    psnakenode pfood; //指向食物的指针
    int score;        //当前累计的分数
    int foodweight;   //一个食物的分数
    int sleeptime;    //蛇休眠的时间.休眠时间越短,蛇移动的速度越快
    
    //游戏当前的状态
    enum GAME_STATUS status;
    //蛇的运动方向
    enum DIRECTION dir;

}snake,psnake;

三.开始游戏

#define _CRT_SECURE_NO_WARNINGS 1
#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(35,10);
	printf("欢迎来到贪吃蛇");
	SetPos(37,20);
	system("pause");//请按任意键继续
	system("cls");//清理屏幕上的信息
	//功能介绍
	SetPos(15, 10);
	printf("用↑,↓,←,→来控制蛇的移动,F3加速,F4减速");
	SetPos(15, 11);
	printf("加速可获得更高的分数");
	SetPos(37, 20);
	system("pause");
	system("cls");
}

void creativemap()
{
	int i = 0;
	//上
	SetPos(0, 0);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 0; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 0; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	system("pause");
}

void gamestart(psnake ps)
{
	//设置控制台信息,窗口大小,窗口名
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(handle, &CursorInfo);//获取光标信息
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(handle, &CursorInfo);

	//打印欢迎信息
	welcome();

	//绘制地图
	creativemap();
}

绘制地图:

void creativemap()
{
	int i = 0;
	//上
	SetPos(0, 0);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 0; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 0; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}

}

蛇的初始化

void InitSnake(psnake ps)
{
	//创建5个蛇身节点
	psnakenode pcur = NULL;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		pcur = (psnakenode)malloc(sizeof(snakenode));
		if (pcur == NULL)
		{
			perror("InitSnake():malloc()");
			return;
		}
		pcur->x = POS_X + 2 * i;
		pcur->y = POS_Y;
		pcur->next = NULL;

		//进行头插
		if (ps->psnake == NULL)
		{
			ps->psnake = pcur;
		}
		else
		{
			pcur->next = ps->psnake;
			ps->psnake = pcur;
		}
	}

	//打印蛇身
	pcur = ps->psnake;
	while (pcur)
	{
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}

	//贪吃蛇的其他信息
	ps->dir = RIGHT;
	ps->foodweight = 1;
	ps->pfood = NULL;
	ps->score = 0;
	ps->sleeptime = 200;//0.2秒
	ps->status = OK;
	getchar();
}

食物的初始化:

1.食物在地图中应该是随机出现的,因此食物的坐标需要随机

2.食物的坐标必须出现在墙内(2<=x<=54,1<=y<=15)

3.食物不能出现在蛇身上

void creatfood(psnake ps)
{
	int x;
	int y;

again:
	do
	{
		x = rand() % 52 + 2;//x:(0 - 52) + 2
		y = rand() % 24 + 1;//y:(0 - 24) + 1
		//当x为奇数时会出现问题,因为蛇身始终处在偶数位
	} while (x % 2 != 0);//当x为偶数时生成成功,若为奇数,重新生成
	//坐标和蛇身坐标比较
	//如果与蛇身重合,则重新生成
	psnakenode pcur = ps->psnake;
	while (pcur)
	{
		if (x == pcur->x && y == pcur->y)
		{
			goto again;
		}
		pcur = pcur->next;
	}

	//创建食物
	psnakenode pfood = (psnakenode)malloc(sizeof(snakenode));
	if (pfood == NULL)
	{
		perror("CreateFood()::malloc()");
		return;
	}
	pfood->x = x;
	pfood->y = y;
	ps->pfood = pfood;
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	getchar();
}

游戏的运行:

void pause()
{
	while (1)
	{
		Sleep(100);
		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;
	}
	else
		return 0;
}

void EatFood(psnake ps, psnakenode pnext)
{
	//ps->psnake是头节点
	pnext->next = ps->psnake;
	ps->psnake = pnext;

	//打印蛇身
	psnakenode pcur = ps->psnake;
	while (pcur)
	{
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	ps->score += ps->foodweight;
	//删除旧的食物
	free(ps->pfood);
	//新建食物
	creatfood(ps);
}

void NotEatFood(psnake ps, psnakenode pnext)
{
	//先头插
	pnext->next = ps->psnake;
	ps->psnake = pnext;
	//删除尾节点
	//同时打印蛇身
	psnakenode pcur = ps->psnake;
	while (pcur->next->next!= NULL)
	{
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	//将尾节点变为空白字符
	SetPos(pcur->next->x, pcur->next->y);
	printf("  ");
	free(pcur->next);
	pcur->next = NULL;
	
}

void KillByWall(psnake ps)
{
	if (ps->psnake->x == 0 || 
		ps->psnake->x == 56 || 
		ps->psnake->y == 0 || 
		ps->psnake->y == 26)
	{
		ps->status = KILL_BY_WALL;
	}
}

void KillBySelf(psnake ps)
{
	psnakenode pcur = ps->psnake->next;
	while (pcur)
	{
		if (pcur->x == ps->psnake->x && pcur->y == ps->psnake->y)
		{
			ps->status = KILL_BY_SELF;
			return;
		}
		pcur = pcur->next;
	}
}

void snakemove(psnake ps)
{
	psnakenode pnext = (psnakenode)malloc(sizeof(snakenode));
	if (pnext == NULL)
	{
		perror("snakemove()::malloc()");
		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
	{
		//不是食物,正常向下走
		NotEatFood(ps, pnext);
	}
	KillByWall(ps);
	KillBySelf(ps);
}



void gamerun(psnake ps)
{
	//打印帮助信息
	PrintHelpInfo();
	
	//对于游戏的循环结构,一般使用do while
	do
	{
	//当前分数
		SetPos(62, 10);
		printf("总分:%6d\n", ps->score);
		SetPos(62, 11);
		printf("食物分值:%02d\n", ps->foodweight);
	//检测按键
		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;
		}
		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 >= 50)
			{ 
				ps->sleeptime -= 50; 
				ps->score += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			if (ps->sleeptime <= 400)
			{
				ps->sleeptime += 50;
				ps->foodweight -= 2;
			}
		}
	//睡眠一会
		Sleep(ps->sleeptime);
	//走一步
		snakemove(ps);
	} while (ps->status == OK);

}

四.游戏结束

void GameEnd(psnake ps)
{
	SetPos(16, 12);
	switch (ps->status)
	{
	case ESC:
		printf("退出游戏");
		break;
	case KILL_BY_WALL:
		printf("撞墙了");
		break;
	case KILL_BY_SELF:
		printf("吃到自己了");
		break;
	}

	psnakenode pcur = ps->psnake;
	psnakenode del = NULL;
	while (pcur)
	{
		del = pcur;
		pcur = pcur->next;
		free(del);
	}
	
	free(ps->pfood);
	ps = NULL;
}

你可能感兴趣的:(游戏,数据结构,链表)