15分钟带你用C++代码实现贪吃蛇游戏

1,399 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

10分钟带你用C++代码实现贪吃蛇游戏

贪吃蛇游戏是一款休闲益智类游戏,既简单又耐玩。该游戏通过控制蛇头方向吃蛋,从而使得蛇变得越来越长,作为初学者,贪吃蛇这样的游戏作为入门再好不过了。下面我们用代码实现贪吃蛇游戏吧! 在这里插入图片描述


语言: C++ 环境:VS2012及以上版本


前言

相信很多人写贪吃蛇的时候,脑袋里冒出了无数的想法。

蛇长什么样?食物长什么样?怎么去计算是否碰到了墙壁?怎么去计算是否碰到了蛇身?怎么增加蛇的身体长度?怎么让蛇自动移动?怎么控制蛇的方向?

一下子N个问题冒出来需要处理,是很难理清楚逻辑和规律的,然后很多同学就会崩溃,无法继续编码。

所以,与其同时思考N个问题,不如先集中注意力,解决第一个小问题。这就是分治法的精髓所在,分而治之,把大问题拆成小问题,然后着力先解决一个个小问题,解决不了的问题放后面,先把能解决的解决掉。

后面还有问题怎么办呢?到了那步再说呗。

游戏规则

用WASD控制蛇的方向,寻找吃的东西,每吃一口就能得到一定的积分,而且蛇的身子会越吃越长,身子越长玩的难度就越大,不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴

逻辑分析

1 控制部分 就是通过输入输出来控制蛇的运动
2 逻辑部分 进行判断蛇吃了没有 是否撞墙 同时把蛇的长度增加一节 还要实现分数的计算
3 图象显示部分 就是将游戏显示出来


一、蛇部分

       1.一条蛇最关键的就是蛇头的位置和蛇身的长度,将蛇看为一个vector数组,存放蛇头到蛇尾的坐标;

       2.每次移动,蛇头向前进方向移动一位,后面的蛇身分别继承上一级的位置,这样就实现了蛇的移动;

       3.当蛇吃到食物时,新创造一个点继承蛇尾的位置。

       4.当蛇碰撞到墙体或者自己的身体,则游戏结束,显示总分

蛇虽然是分成很多块,但他们还是一个整体,每一块的移动都和上一块有关,所以不需要对每一块都进行判断。我们可以把蛇身体的每一块看成一个对象(对象存储该块的坐标和相关信息),作为节点存储在线性链表中,再设置一个变量标志蛇的方向(通过按键可以改变)。一般人都是让每一个节点等于他指向的下一个节点,并让头节点改变位置来实现转弯和移动,这个算法复杂度太高(O(n)),实际上只要做两步操作,插入一个头节点,删除一个尾节点就可以了,新插入的头节点位置根据蛇当前的方向决定.

1、Snake.h

Snake.h代码如下:

#pragma once

#include<vector>
#include<windows.h>
#include "Food.h"

using namespace std;
//上下左右
#define DIR_UP      1
#define DIR_RIGHT   2
#define DIR_DOWN    3
#define DIR_LEFT    4
class Snake
{
public:
	Snake(void);
	~Snake(void);

	vector<COORD>     n_bodyPos;
	int				  n_bodyLen;
	HANDLE            n_hOut;

	void show();
	void disappear();
	void init(int len,COORD startPos);
	void move(int dir,Food thFood);
	void move(int dir);
	void grow();
	void keyBdCtr(Food thFood);
};

2、Snake.cpp

Snake.cpp代码如下:

#include "stdafx.h"
#include "Snake.h"
#include "Food.h"
#include<windows.h>
#include<conio.h>

Snake::Snake(void)
{
	n_bodyPos.clear();
	n_bodyLen=0;
}


Snake::~Snake(void)
{
}

void Snake::init(int len,COORD startPos)
{
	for(int i=0;i<len;i++)
	{
		COORD tmp=startPos;
		tmp.X=startPos.X-i;
		n_bodyPos.push_back(tmp);
		n_bodyLen++;
	}
}

void Snake::show()
{
	n_hOut=GetStdHandle(STD_OUTPUT_HANDLE);
	for(int i=0;i<n_bodyLen;i++)
	{
		SetConsoleCursorPosition(n_hOut,n_bodyPos[i]);
		if(i==0)
			printf("@");
		else
			printf("*");
	}
}

void Snake::move(int dir,Food thFood)
{
	Sleep(500);    //间隔一段时间
	//system("cls");
	//蛇身体消失
	disappear();

	for(int i=n_bodyPos.size()-1;i>=1;i--)
	{
		n_bodyPos[i]=n_bodyPos[i-1];
		SetConsoleCursorPosition(n_hOut,n_bodyPos[i]);
		printf("*");
	}

	switch (dir)
	{
	case DIR_UP:
		{
			n_bodyPos[0].Y-=1;
			break;
		}
	case DIR_RIGHT:
		{
			n_bodyPos[0].X+=1;
			break;
		}
	case DIR_DOWN:
		{
			n_bodyPos[0].Y+=1;
			break;
		}
	case DIR_LEFT:
		{
			n_bodyPos[0].X-=1;
			break;
		}
	default:
		break;
	}
	
	SetConsoleCursorPosition(n_hOut,n_bodyPos[0]);
	printf("*");

	//判断碰撞
	if(thFood.n_pos.X==n_bodyPos[0].X&&
		thFood.n_pos.Y==n_bodyPos[0].Y)
	{
		grow();
		thFood.disappear();
	}

}

void Snake::move(int dir)
{
	Sleep(500);    //间隔一段时间
	//system("cls");
	//蛇身体消失
	disappear();

	for(int i=n_bodyPos.size()-1;i>=1;i--)
	{
		n_bodyPos[i]=n_bodyPos[i-1];
		SetConsoleCursorPosition(n_hOut,n_bodyPos[i]);
		printf("*");
	}

	switch (dir)
	{
	case DIR_UP:
		{
			n_bodyPos[0].Y-=1;
			break;
		}
	case DIR_RIGHT:
		{
			n_bodyPos[0].X+=1;
			break;
		}
	case DIR_DOWN:
		{
			n_bodyPos[0].Y+=1;
			break;
		}
	case DIR_LEFT:
		{
			n_bodyPos[0].X-=1;
			break;
		}
	default:
		break;
	}
	
	SetConsoleCursorPosition(n_hOut,n_bodyPos[0]);
	printf("@");

}

void Snake::grow()
{
	COORD tmp=n_bodyPos[n_bodyLen-1];
	n_bodyPos.push_back(tmp);
	n_bodyLen++;
}

void Snake::disappear()
{
	for(int i=0;i<n_bodyLen;i++)
	{
		SetConsoleCursorPosition(n_hOut,n_bodyPos[i]);
		printf(" ");
	}
}

void Snake::keyBdCtr(Food thFood)
{
	while(1)
	{
		char ch=' ';
		if(_kbhit())
		{
			ch=_getch();
			switch (ch)
			{
			case 'w':
				{
					move(DIR_UP,thFood);
					break;
				}
			case 'a':
			{
				move(DIR_LEFT,thFood);
				break;
			}
			case 's':
			{
				move(DIR_DOWN,thFood);
				break;
			}
			case 'd':
			{
				move(DIR_RIGHT,thFood);
				break;
			}
			default:
				break;
			}
		}
	}
}

二、食物部分

       食物是一个用坐标表示的特殊点,当蛇头碰撞到食物点,食物消失,系统加分并蛇身加长。        再食物被吃掉之后,调用随机函数重新生成食物。

1.Food.h

Food.h代码如下:

#pragma once
#include<windows.h>


class Food
{
public:
	Food(void);
	~Food(void);

	COORD			     n_pos;
	HANDLE               n_hOut;
	int                  n_stat;
	int                  score;
	static int           n_firstRun;

	int create();
	int create(COORD pos);
	void disappear();
};

2.Food.cpp

Food.cpp代码如下:

#include "stdafx.h"
#include "Food.h"
#include<windows.h>
#include <time.h>

int Food::n_firstRun=0;

Food::Food(void)
{
	if(Food::n_firstRun==0)
	{
		srand((unsigned)time(NULL));
		Food::n_firstRun=1;
	}
	n_hOut=GetStdHandle(STD_OUTPUT_HANDLE);
}


Food::~Food(void)
{
}

int Food::create()
{
	score=rand()%10+1;
	n_pos.X=rand()%20+1;
	n_pos.Y=rand()%20+1;
	SetConsoleCursorPosition(n_hOut,n_pos);
	printf("&");
	return 0;
}

int Food::create(COORD pos)
{
	score=1;
	n_pos=pos;
	SetConsoleCursorPosition(n_hOut,n_pos);
	printf("&");
	return 0;
}

void Food::disappear()
{
	n_stat=0;
}

三、墙体部分

       墙体相对而言简单点,只需要初始化并显示,然后蛇运动时判断是否碰撞墙体

1.Wall.h

Wall.h代码如下:

#pragma once

#include<vector>
#include<windows.h>

using namespace std;

class Wall
{
public:
	Wall(void);
	~Wall(void);

	vector<COORD>     n_bodyPos;

	void init();
	void show();
};

2.Wall.cpp

#include "stdafx.h"
#include "Wall.h"


Wall::Wall(void)
{
	n_bodyPos.clear();
}


Wall::~Wall(void)
{
}

void Wall::init()
{
	for(int i=0;i<22;i++)
	{
		COORD tmp1={0,0};
		COORD tmp=tmp1;
		tmp.X=tmp1.X+i;
		n_bodyPos.push_back(tmp);
	}
	for(int i=0;i<22;i++)
	{
		COORD tmp1={0,0};
		COORD tmp=tmp1;
		tmp.Y=tmp1.Y+i;
		n_bodyPos.push_back(tmp);
	}
	for(int i=0;i<22;i++)
	{
		COORD tmp1={21,21};
		COORD tmp=tmp1;
		tmp.X=tmp1.X-i;
		n_bodyPos.push_back(tmp);
	}
	for(int i=0;i<22;i++)
	{
		COORD tmp1={21,21};
		COORD tmp=tmp1;
		tmp.Y=tmp1.Y-i;
		n_bodyPos.push_back(tmp);
	}
}

void Wall::show()
{
	HANDLE n_hOut=GetStdHandle(STD_OUTPUT_HANDLE);
	for(int i=0;i<n_bodyPos.size();i++)
	{
		SetConsoleCursorPosition(n_hOut,n_bodyPos[i]);
		printf("#");
	}
}

四、游戏主体部分

1.Game.h

Game.h代码如下:

#pragma once

#include "Snake.h"
#include "Food.h"
#include "Wall.h"


class Game
{
public:
   Game(void);
   ~Game(void);

   Snake      n_snake;
   Food       n_food;
   Wall       n_wall;
   int        score;
   HANDLE     n_hOut;

   int init();
   int start();
   int end();
   void keyBdCtr();

};

2.Game.cpp

#include "stdafx.h"
#include "Game.h"
#include "Snake.h"
#include "Food.h"
#include<conio.h>
#include<windows.h>

Game::Game(void)
{
	n_hOut=GetStdHandle(STD_OUTPUT_HANDLE);
}


Game::~Game(void)
{
}

int Game::init()
{
	score=0;

	n_wall.init();
	n_wall.show();

	COORD pos={10,10};
	n_snake.init(5,pos);
	n_snake.show();

	COORD pos2={15,10};
	n_food.create(pos2);

	COORD pos3={0,22};
	SetConsoleCursorPosition(n_hOut,pos3);
	printf("当前总得分:%d",score);

	COORD pos4={0,23};
	SetConsoleCursorPosition(n_hOut,pos4);
	printf("当前食物分数:%d",n_food.score);
	return 0;
}

int Game::start()
{
	keyBdCtr();
	return 0;
}

int Game::end()
{
	COORD pos={0,24};
	SetConsoleCursorPosition(n_hOut,pos);
	printf("游戏结束!\n");
	printf("总得分:%d\n",score);
	system("pause");
	return 0;
}

void Game::keyBdCtr()
{
	char ch=' ';
	while(1)
	{
		if(_kbhit())
		{
			ch=_getch();
		}

		
		switch (ch)
		{
		case 'w':
			{
				n_snake.move(DIR_UP);
				break;
			}
		case 'a':
			{
				n_snake.move(DIR_LEFT);
				break;
			}
		case 's':
			{
				n_snake.move(DIR_DOWN);
				break;
			}
		case 'd':
			{
				n_snake.move(DIR_RIGHT);
				break;
			}
		default:
			break;
		}
		Sleep(500);

		//判断是否撞墙,结束
		for(int i=0;i<n_wall.n_bodyPos.size();i++)
		{
			if(n_wall.n_bodyPos[i].X==n_snake.n_bodyPos[0].X&&
				n_wall.n_bodyPos[i].Y==n_snake.n_bodyPos[0].Y)
			{
				end();
			}
		}

		//判断蛇咬身体,结束
		for(int i=1;i<n_snake.n_bodyLen;i++)
		{
			if(n_snake.n_bodyPos[i].X==n_snake.n_bodyPos[0].X&&
				n_snake.n_bodyPos[i].Y==n_snake.n_bodyPos[0].Y)
			{
				end();
			}
		}

		//判断与食物碰撞
		if(n_food.n_pos.X==n_snake.n_bodyPos[0].X&&
			n_food.n_pos.Y==n_snake.n_bodyPos[0].Y)
		{
			score+=n_food.score;
			COORD pos={0,22};
			SetConsoleCursorPosition(n_hOut,pos);
			printf("当前总得分:%d",score);

			n_snake.grow();
			n_food.disappear();
			Sleep(500);
			n_food.create();

			COORD pos1={0,23};
			SetConsoleCursorPosition(n_hOut,pos1);
			printf("当前食物分数:%d",n_food.score);

		}
	}
}

五、主函数测试

#include "stdafx.h"
#include<stdio.h>
#include<windows.h>
#include<conio.h>
#include "Snake.h"
#include "Food.h"
#include "Game.h"
#include <time.h>


#define MAXLEN 30

int _tmain(int argc, _TCHAR* argv[])
{
	Game thgame;
	thgame.init();
	thgame.start();
		return  0;
}

六、运行结果展示

在这里插入图片描述

七、附录:vector数组

vector数组是一个能存放任意数据类型(类,结构,普通变量类型等)的动态数组!,在数据结构中就相当于顺序储存的线性表,寻找元素非常快,但是插入元素的时间却很大(list是一个双向链表,在同一个为止插入大量的数据时速度很快,但是查找的速度就会慢很多)

vector和普通数组一样可以通过下标索引来进行访问!与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好。