贪吃蛇——c语言版

06-27 981阅读

文章目录

  • 演示效果
  • 实现的基本功能
  • 技术要点
  • 源代码
  • 实现功能
    • GameStart
      • 打印欢迎界面和功能介绍
      • 绘制地图
      • 创建蛇
      • 创建食物
      • GameRun
        • 打印提示信息
        • 蛇每走一步
        • GameEnd
        • 蛇死亡后继续游戏

          演示效果

          贪吃蛇1.0演示视频

          贪吃蛇——c语言版

          将终端应用程序改为控制台主机

          实现的基本功能

          1. 贪吃蛇地图绘制
          2. 蛇吃食物的功能(上、下、左、右⽅向键控制蛇的动作)
          3. 蛇撞墙死亡
          4. 蛇撞⾃⾝死亡
          5. 计算得分
          6. 蛇⾝加速、减速
          7. 暂停游戏

          技术要点

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

          源代码

          snake.h

          #pragma once
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #include 
          #define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?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 CONDITION
          { 
          	OK,
          	KILL_BY_WALL,
          	KILL_BY_SELF,
          	END_NORMAL
          };
          typedef struct SnakeNode
          {
          	int x;
          	int y;
          	struct SnakeNode* next;
          }SnakeNode,* pSnakeNode;
          typedef struct Snake
          {
          	pSnakeNode pSnakebody;//指向蛇头的节点
          	pSnakeNode pFood;//指向食物的节点
          	enum DIRECTION Dir;//方向
          	enum CONDITION Cdt;//状态
          	int Food_Weight;//食物的权重
          	int Socre;//总分数
          	int Sleep_Time;//睡眠时间
          }Snake,* pSnake;
          //初始化
          void GameStart(pSnake ps);
          //定位
          void SetPos(short x, short y);
          //打印欢迎界面和功能介绍
          void  Welcome_To_Game();
          //绘制地图
          void CreateMap();
          //创建蛇
          void InitSnake(pSnake ps);
          //创建食物
          void CreateFood(pSnake ps);
          //运行游戏
          void GameRun(pSnake ps);
          //打印提示信息
          void PrintHelpInfo();
          //蛇走的过程
          void SnakeMove(pSnake ps);
          //判断下一个节点是不是食物
          bool NextIsFood(pSnakeNode pn,pSnake ps);
          //如果是食物,就吃掉,然后创建新的食物
          void EatFood(pSnakeNode pn,pSnake ps);
          //如果不是食物,就把最后一个节点释放
          void NotFood(pSnakeNode pn, pSnake ps);
          //判断下一步是否撞墙
          void KillByWall(pSnake ps);
          //判断下一步是否撞到自己
          void KillBySelf(pSnake ps);
          //善后工作
          void GameEnd(pSnake ps);
          

          snake.c

          #define   _CRT_SECURE_NO_WARNINGS 1
          #include "Snake.h"
          void CreateFood(pSnake ps)
          {
          	int x = 0;
          	int y = 0;
          	//食物的坐标在棋盘内
          again:
          	do
          	{
          		x = rand() % 53 + 2;
          		y = rand() % 25 + 1;
          	} while (x % 2 != 0);
          	//食物不能和蛇身冲突
          	pSnakeNode cur = ps->pSnakebody;
          	while (cur)
          	{
          		if (cur->x == x && cur->y == y)
          		{
          			goto again;
          		}
          		cur = cur->next;
          	}
          	//创建食物
          	pSnakeNode Food = (pSnakeNode)malloc(sizeof(SnakeNode));
          	if (Food == NULL)
          	{
          		perror("malloc fail");
          		return;
          	}
          	Food->next = NULL;
          	Food->x = x;
          	Food->y = y;
          	SetPos(x, y);
          	wprintf(L"%lc", FOOD);
          	ps->pFood = Food;
          }
          void SetPos(short x, short y)
          {
          	HANDLE houtput = NULL;
          	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
          	COORD pos = { x,y };
          	SetConsoleCursorPosition(houtput, pos);
          }
          void InitSnake(pSnake ps)
          {
          	ps->pSnakebody = NULL;
          	pSnakeNode cur = NULL;
          	for (int i = 0; i next = NULL;
          		cur->x = POS_X + i * 2;
          		cur->y = POS_Y;
          		//头插
          		if (ps->pSnakebody == NULL)
          		{
          			ps->pSnakebody = cur;
          		}
          		else
          		{
          			cur->next = ps->pSnakebody;
          			ps->pSnakebody = cur;
          		}
          	}
          	//打印身体
          	cur = ps->pSnakebody;
          	while (cur)
          	{
          		SetPos(cur->x, cur->y);
          		wprintf(L"%lc", BODY);
          		cur = cur->next;
          	}
          	//初始化贪吃蛇的数据
          	ps->Cdt = OK;
          	ps->Dir = RIGHT;
          	ps->Food_Weight = 10;
          	ps->Socre = 0;
          	ps->Sleep_Time = 200;//单位毫秒
          }
          void CreateMap()
          {
          	int i = 0;
          	//上
          	for (i = 0; i x && pn->y == ps->pFood->y);
          }
          //如果是食物,就吃掉,然后创建新的食物
          void EatFood(pSnakeNode pn, pSnake ps)
          {
          	//头插
          	ps->pFood->next = ps->pSnakebody;
          	ps->pSnakebody = ps->pFood;
          	//释放下一个位置的节点
          	free(pn);
          	pn = NULL;
          	//打印
          	pSnakeNode cur = ps->pSnakebody;
          	while (cur)
          	{
          		SetPos(cur->x, cur->y);
          		wprintf(L"%lc", BODY);
          		cur = cur->next;
          	}
          	ps->Socre += ps->Food_Weight;
          	CreateFood(ps);
          }
          //如果不是食物,就把最后一个节点释放
          void NotFood(pSnakeNode pn, pSnake ps)
          {
          	//头插
          	pn->next = ps->pSnakebody;
          	ps->pSnakebody = pn;
          	//最后一个节点,要释放
          	pSnakeNode cur = ps->pSnakebody;
          	while (cur->next->next)//打印前n个
          	{
          		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->pSnakebody->x == 56
          		|| ps->pSnakebody->x == 0
          		|| ps->pSnakebody->y == 0
          		|| ps->pSnakebody->y == 26)
          	{
          		ps->Cdt = KILL_BY_WALL;
          	}
          }
          //判断下一步是否撞到自己
          void KillBySelf(pSnake ps)
          {
          	pSnakeNode cur = ps->pSnakebody->next;
          	while (cur)
          	{
          		if (cur->x == ps->pSnakebody->x && cur->y == ps->pSnakebody->y)
          		{
          			ps->Cdt = KILL_BY_SELF;
          		}
          		cur = cur->next;
          	}
          }
          //蛇走的过程
          void SnakeMove(pSnake ps)
          {
          	//创建下一个节点
          	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
          	if (pNextNode == NULL)
          	{
          		perror("pNextNode malloc fail");
          		return;
          	}
          	switch (ps->Dir)
          	{
          	case UP://向上
          		pNextNode->x = ps->pSnakebody->x;
          		pNextNode->y = ps->pSnakebody->y - 1;
          		break;
          	case DOWN://向下
          		pNextNode->x = ps->pSnakebody->x;
          		pNextNode->y = ps->pSnakebody->y + 1;
          		break;
          	case LEFT://向左
          		pNextNode->x = ps->pSnakebody->x - 2;
          		pNextNode->y = ps->pSnakebody->y;
          		break;
          	case RIGHT://向右
          		pNextNode->x = ps->pSnakebody->x + 2;
          		pNextNode->y = ps->pSnakebody->y;
          		break;
          	}
          	//判断下一个节点是不是食物
          	if (NextIsFood(pNextNode, ps))
          	{
          		EatFood(pNextNode, ps);
          	}
          	else
          	{
          		NotFood(pNextNode, ps);
          	}
          	KillByWall(ps);//下一步是墙
          	KillBySelf(ps);//下一步是自己
          }
          //运行游戏
          void GameRun(pSnake ps)
          {
          	PrintHelpInfo();
          	do
          	{
          		SetPos(60, 10);
          		printf("食物分数:%2d", ps->Food_Weight);
          		SetPos(60, 11);
          		printf("总分数:%2d", ps->Socre);
          		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->Cdt = END_NORMAL;
          			break;
          		}
          		else if (KEY_PRESS(VK_SPACE))
          		{
          			pause();
          		}
          		else if (KEY_PRESS(VK_F1))
          		{
          			if (ps->Sleep_Time > 80)
          			{
          				ps->Sleep_Time -= 30;
          				ps->Food_Weight += 2;
          			}
          		}
          		else if (KEY_PRESS(VK_F2))
          		{
          			if (ps->Sleep_Time Sleep_Time += 30;
          				ps->Food_Weight -= 2;
          			}
          		}
          		//让蛇走起来
          		SnakeMove(ps);
          		Sleep(ps->Sleep_Time);
          	} while (ps->Cdt == OK);
          	
          }
          //善后工作
          void GameEnd(pSnake ps)
          {
          	SetPos(20, 12);
          	switch (ps->Cdt)
          	{
          	case END_NORMAL:
          		printf("您主动结束了游戏");
          		break;
          	case KILL_BY_WALL:
          		printf("您撞到墙死了");
          		break;
          	case KILL_BY_SELF:
          		printf("您咬到了自己");
          	}
          	//释放蛇身节点
          	pSnakeNode cur = ps->pSnakebody;
          	while (cur)
          	{
          		pSnakeNode del = cur;
          		cur = cur->next;
          		free(del);
          	}
          }
          

          snaketest.c

          #define   _CRT_SECURE_NO_WARNINGS 1
          #include "Snake.h"
          void test()
          {
          	int ch = 0;
          	do
          	{
          		system("cls");
          		Snake snake;
          		GameStart(&snake);
          		GameRun(&snake);
          		GameEnd(&snake);
          		SetPos(20, 14);
          		printf("再来一局吗?(Y/N):");
          		ch = getchar();
          		while (getchar() != '\n');
          	} while (ch == 'Y' || ch == 'y');
          	SetPos(0, 27);
          }
          int main()
          {
          	//创建贪吃蛇
          	setlocale(LC_ALL, "");
          	srand((unsigned int)time(NULL));
          	test();
          	return 0;
          	//初始化游戏
          	//运行游戏
          	//结束游戏
          }
          

          实现功能

          在地图上我们打印墙体、蛇、食物都是使用宽字符,普通字符只占字节,宽字符占两个

          中文也是两个宽字符,所以我们本地化所有类项即可

          setlocale(LC_ALL, "");
          

          再实现功能以前,我们可以将要实现的功能分成三大类:

          GameStart(初始化游戏)

          GameRun(运行游戏)

          GameEnd(游戏善后工作)

          GameStart

          再绘制地图以前,我们需要设置控制台窗口的长宽:100列,30行

          顺便设置窗口的名字为贪吃蛇

          void GameStart(pSnake ps)
          {
          	system("mode con cols=100 lines=30");
          	system("title 贪吃蛇");
          }
          

          隐藏光标并且获得句柄

          void GameStart(pSnake ps)
          {
          	system("mode con cols=100 lines=30");
          	system("title 贪吃蛇");
          	//获取标准输出设备的句柄
          	HANDLE houtput = NULL;
          	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
          	//定义光标信息结构体
          	CONSOLE_CURSOR_INFO CursorInfo;
          	//获取光标信息
          	GetConsoleCursorInfo(houtput, &CursorInfo);
          	//修改
          	CursorInfo.bVisible = false;
          	//设置
          	SetConsoleCursorInfo(houtput, &CursorInfo);
          }
          

          修改光标位置

          void SetPos(short x, short y)
          {
          	HANDLE houtput = NULL;
          	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
          	COORD pos = { x,y };
          	SetConsoleCursorPosition(houtput, pos);
          }
          

          打印欢迎界面和功能介绍

          前面提到如何定位光标位置,在每一页打印完以后暂停,并且清理该页,才会接着打印下一页

          void Welcome_To_Game()
          {
          	SetPos(40, 14);
          	printf("欢迎来到贪食蛇小游戏");
          	SetPos(40, 25);
          	system("pause");
          	system("cls");
          	SetPos(25, 14);
          	printf("用↑ . ↓ . ← . → 分别控制蛇的移动,F1为加速,");
          	SetPos(25, 15);
          	printf("F2为减速,加速将得到更高的分数");
          	SetPos(40, 24);
          	system("pause");
          	system("cls");
          }
          

          绘制地图

          因为在后续功能中经常要用到各种图案,我们就统一在头文件里定义好

          #define WALL L'□'
          #define BODY L'●'
          #define FOOD L'★'
          
          void CreateMap()
          {
          	int i = 0;
          	//上
          	for (i = 0; i x = POS_X + i * 2;
          		cur->y = POS_Y;
          		//头插
          		if (ps->pSnakebody == NULL)
          		{
          			ps->pSnakebody = cur;
          		}
          		else
          		{
          			cur->next = ps->pSnakebody;
          			ps->pSnakebody = cur;
          		}
          	}
          	//打印身体
          	cur = ps->pSnakebody;
          	while (cur)
          	{
          		SetPos(cur->x, cur->y);
          		wprintf(L"%lc", BODY);
          		cur = cur->next;
          	}
          	//初始化贪吃蛇的数据
          	ps->Cdt = OK;
          	ps->Dir = RIGHT;
          	ps->Food_Weight = 10;
          	ps->Socre = 0;
          	ps->Sleep_Time = 200;//单位毫秒
          }
          

          创建食物

          棋盘大小:

          贪吃蛇——c语言版

          要注意食物的范围

          void CreateFood(pSnake ps)
          {
          	int x = 0;
          	int y = 0;
          	//食物的坐标在棋盘内随机生成
          again:
          	do
          	{
          		x = rand() % 53 + 2;
          		y = rand() % 25 + 1;
          	} while (x % 2 != 0);
          	//食物不能和蛇身冲突
          	pSnakeNode cur = ps->pSnakebody;
          	while (cur)
          	{
          		if (cur->x == x && cur->y == y)
          		{
          			goto again;
          		}
          		cur = cur->next;
          	}
          	//创建食物
          	pSnakeNode Food = (pSnakeNode)malloc(sizeof(SnakeNode));
          	if (Food == NULL)
          	{
          		perror("malloc fail");
          		return;
          	}
          	Food->next = NULL;
          	Food->x = x;
          	Food->y = y;
          	SetPos(x, y);
          	wprintf(L"%lc", FOOD);
          	ps->pFood = Food;
          }
          

          GameRun

          打印提示信息

          贪吃蛇——c语言版

          //打印提示信息
          void PrintHelpInfo()
          {
          	SetPos(60, 15);
          	printf("不能穿墙,不能咬到自己");
          	SetPos(60, 16);
          	printf("用 ↑.↓.←.→ 分别控制蛇的移动,");
          	SetPos(60, 17);
          	printf("F1为加速,F2为减速,加速将得到更高的分数");
          	SetPos(60, 18);
          	printf("ESC:退出游戏,SPACE:暂停游戏");
          	SetPos(60, 20);
          	printf("我爱吃福鼎肉片@版权");
          }
          

          蛇每走一步

          当蛇正在向某个方向走时,不能操控蛇走相反方向!

          #define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
          
          //运行游戏
          void GameRun(pSnake ps)
          {
          	PrintHelpInfo();
          	do
          	{
          		SetPos(60, 10);
          		printf("食物分数:%2d", ps->Food_Weight);
          		SetPos(60, 11);
          		printf("总分数:%2d", ps->Socre);
          		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->Cdt = END_NORMAL;
          			break;
          		}
          		else if (KEY_PRESS(VK_SPACE))
          		{
          			pause();
          		}
          		else if (KEY_PRESS(VK_F1))
          		{
          			if (ps->Sleep_Time > 80)
          			{
          				ps->Sleep_Time -= 30;
          				ps->Food_Weight += 2;
          			}
          		}
          		else if (KEY_PRESS(VK_F2))
          		{
          			if (ps->Sleep_Time Sleep_Time += 30;
          				ps->Food_Weight -= 2;
          			}
          		}
          		//让蛇走起来
          		SnakeMove(ps);
          		Sleep(ps->Sleep_Time);
          	} while (ps->Cdt == OK);//状态为OK才能走
          	
          }
          

          当蛇走向下一个节点时,要分为两种情况

          1. 下个节点是食物
          2. 下个节点不是食物
          //蛇走的过程
          void SnakeMove(pSnake ps)
          {
          	//创建下一个节点
          	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
          	if (pNextNode == NULL)
          	{
          		perror("pNextNode malloc fail");
          		return;
          	}
          	switch (ps->Dir)
          	{
          	case UP://向上
          		pNextNode->x = ps->pSnakebody->x;
          		pNextNode->y = ps->pSnakebody->y - 1;
          		break;
          	case DOWN://向下
          		pNextNode->x = ps->pSnakebody->x;
          		pNextNode->y = ps->pSnakebody->y + 1;
          		break;
          	case LEFT://向左
          		pNextNode->x = ps->pSnakebody->x - 2;
          		pNextNode->y = ps->pSnakebody->y;
          		break;
          	case RIGHT://向右
          		pNextNode->x = ps->pSnakebody->x + 2;
          		pNextNode->y = ps->pSnakebody->y;
          		break;
          	}
          	//判断下一个节点是不是食物
          	if (NextIsFood(pNextNode, ps))
          	{
          		EatFood(pNextNode, ps);
          	}
          	else
          	{
          		NotFood(pNextNode, ps);
          	}
          	KillByWall(ps);//下一步是墙
          	KillBySelf(ps);//下一步是自己
          }
          
          1. 下个节点是食物

            先判断一下

          //判断下一个节点是不是食物
          bool NextIsFood(pSnakeNode pn, pSnake ps)
          {
          	return (pn->x == ps->pFood->x && pn->y == ps->pFood->y);
          }
          
          //如果是食物,就吃掉,然后创建新的食物
          void EatFood(pSnakeNode pn, pSnake ps)
          {
          	//头插
          	ps->pFood->next = ps->pSnakebody;
          	ps->pSnakebody = ps->pFood;
          	//释放下一个位置的节点
          	free(pn);
          	pn = NULL;
          	//打印
          	pSnakeNode cur = ps->pSnakebody;
          	while (cur)
          	{
          		SetPos(cur->x, cur->y);
          		wprintf(L"%lc", BODY);
          		cur = cur->next;
          	}
          	ps->Socre += ps->Food_Weight;
          	CreateFood(ps);
          }
          

          为什么释放下一个位置的节点,有些同学有疑问

          因为在CreateFood函数中,已经将ps->Food指向了食物节点,所以下一个位置的节点用不上的,当然要释放掉,因此,使用下一个位置的节点,释放食物节点也是可以的。

          2. 下一个节点不是食物

          贪吃蛇——c语言版

          //如果不是食物,就把最后一个节点释放
          void NotFood(pSnakeNode pn, pSnake ps)
          {
          	//头插
          	pn->next = ps->pSnakebody;
          	ps->pSnakebody = pn;
          	//最后一个节点,要释放
          	pSnakeNode cur = ps->pSnakebody;
          	while (cur->next->next)//打印前n个
          	{
          		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->pSnakebody->x == 56
          		|| ps->pSnakebody->x == 0
          		|| ps->pSnakebody->y == 0
          		|| ps->pSnakebody->y == 26)
          	{
          		ps->Cdt = KILL_BY_WALL;
          	}
          }
          //判断下一步是否撞到自己
          void KillBySelf(pSnake ps)
          {
          	pSnakeNode cur = ps->pSnakebody->next;
          	while (cur)
          	{
          		if (cur->x == ps->pSnakebody->x && cur->y == ps->pSnakebody->y)
          		{
          			ps->Cdt = KILL_BY_SELF;
          		}
          		cur = cur->next;
          	}
          }
          

          GameEnd

          贪吃蛇——c语言版

          //善后工作
          void GameEnd(pSnake ps)
          {
          	SetPos(20, 12);
          	switch (ps->Cdt)
          	{
          	case END_NORMAL:
          		printf("您主动结束了游戏");
          		break;
          	case KILL_BY_WALL:
          		printf("您撞到墙死了");
          		break;
          	case KILL_BY_SELF:
          		printf("您咬到了自己");
          	}
          	//释放蛇身节点
          	pSnakeNode cur = ps->pSnakebody;
          	while (cur)
          	{
          		pSnakeNode del = cur;
          		cur = cur->next;
          		free(del);
          	}
          }
          

          蛇死亡后继续游戏

          到此还没有结束,如果玩家还想在玩一把呢,总不能关闭应用再重新点开吧

          void test()
          {
          	int ch = 0;
          	do
          	{
          		system("cls");
          		Snake snake;
          		GameStart(&snake);
          		GameRun(&snake);
          		GameEnd(&snake);
          		SetPos(20, 14);
          		printf("再来一局吗?(Y/N):");
          		ch = getchar();
          		while (getchar() != '\n');//如果输入yyyy一样可以读取
          	} while (ch == 'Y' || ch == 'y');
          	SetPos(0, 27);
          }
          

          贪吃蛇——c语言版

          希望这篇博客对你有所帮助!!!如果有不懂或者有错的地方欢迎私信探讨!

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]