贪吃蛇c++

来源

5.贪吃蛇-从头开始编程_哔哩哔哩_bilibili

源代码链接

https://github.com/jpromanonet/snakeCPP/blob/master/snakeCPP.cpp#L24C2-L24C2

我对代码进行了理解,并进行了改写,代码如下。因为水平有限,理解有误的地方,敬请指正。

#include <iostream>
#include <list>
#include <thread>
#include <Windows.h>

using namespace std;

int nScreenWidth = 120;
int nScreenHeight = 30;
int step = 1;

struct sSnakeSegment {
	int x;
	int y;
};

list<sSnakeSegment> snake;
int nFoodX ;
int nFoodY ;
int nScore ;
int nSnakeDirection ;
bool bDead ;
bool bKeyLeft , bKeyRight , bKeyUp , bKeyDown ;
wchar_t* screen = new wchar_t[nScreenWidth * nScreenHeight];
void initGame()
{
	 //snake = { {60,15},{61,15},{62,15},{63,15},{64,15},{65,15},{66,15},{67,15},{68,15},{69,15} };
	 snake = { {60,15},{61,15} };
	 nFoodX = 30;
	 nFoodY = 15;
	 nScore = 0;
	 nSnakeDirection = 3;
	 bDead = false;
	 bKeyLeft = false, bKeyRight = false, bKeyUp = false, bKeyDown = false;
}

void keyEvent(void)
{
	bKeyRight = (0x8000 & GetAsyncKeyState((unsigned char)('\x27'))) != 0;
	bKeyLeft = (0x8000 & GetAsyncKeyState((unsigned char)('\x25'))) != 0;
	bKeyDown = (0x8000 & GetAsyncKeyState((unsigned char)('\x28'))) != 0;
	bKeyUp = (0x8000 & GetAsyncKeyState((unsigned char)('\x26'))) != 0;

	if (bKeyUp)
	{
		if (nSnakeDirection != 2)
			nSnakeDirection = 0;
	}
	if (bKeyRight)
	{
		if (nSnakeDirection != 3)
			nSnakeDirection = 1;
	}

	if (bKeyDown)
	{
		if (nSnakeDirection != 0)
			nSnakeDirection = 2;
	}
	if (bKeyLeft)
	{
		if (nSnakeDirection != 1)
			nSnakeDirection = 3;
	}
	

}

void SnakeDir(void)
{
	switch (nSnakeDirection)
	{

	case 0: // Up
		snake.push_front({ snake.front().x, snake.front().y - step });
		break;

	case 1: // Right
		snake.push_front({ snake.front().x + step, snake.front().y });
		break;

	case 2:  // Down
		snake.push_front({ snake.front().x, snake.front().y + step });
		break;

	case 3:  // Left
		snake.push_front({ snake.front().x - step, snake.front().y });
		break;
	}


	snake.pop_back();
}

void eatFood()
{
	if (snake.front().x == nFoodX && snake.front().y == nFoodY) {
		nScore++;
		while (screen[nFoodY * nScreenWidth + nFoodX] != L' ') {
			nFoodX = rand() % nScreenWidth;
			nFoodY = (rand() % (nScreenHeight - 3)) + 3;
		}

		snake.push_back({ snake.back().x, snake.back().y });
	}

}

void DeadDetect(void)
{
	if (snake.front().x < 0 || snake.front().x >= nScreenWidth)
		bDead = true;
	if (snake.front().y < 3 || snake.front().y >= nScreenHeight)
		bDead = true;

	for (list<sSnakeSegment>::iterator i = snake.begin(); i != snake.end(); i++)
		if (i != snake.begin() && i->x == snake.front().x && i->y == snake.front().y)
			bDead = true;
}

void updateScreen(void)
{
	
	for (int i = 0; i < nScreenWidth * nScreenHeight; i++) screen[i] = L' ';
	for (int i = 0; i < nScreenWidth; i++) {
		screen[i] = L' ';
		screen[2 * nScreenWidth + i] = L'=';
	}
	wsprintf(&screen[nScreenWidth * 3 / 2], L"SCORE: %d", nScore);

	
	for (auto s : snake)
		screen[s.y * nScreenWidth + s.x] = bDead ? L'+' : L'O';

	
	screen[snake.front().y * nScreenWidth + snake.front().x] = bDead ? L'X' : L'@';

	
	screen[nFoodY * nScreenWidth + nFoodX] = L'%';

	if (bDead)
		wsprintf(&screen[15 * nScreenWidth + 40], L"    PRESS 'SPACE' TO PLAY AGAIN    ");
}
int main()
{


	for (int i = 0; i < nScreenWidth * nScreenHeight; i++) screen[i] = L' ';
	HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	SetConsoleActiveScreenBuffer(hConsole);
	DWORD dwBytesWritten = 0;

	while (1) {
		initGame();
		while (!bDead) {
			auto  t1 = chrono::system_clock::now();
			while ((chrono::system_clock::now() - t1) < 200ms)
			{
				keyEvent();
			}
			
			SnakeDir();
			eatFood();
			DeadDetect();
			updateScreen();

			WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten);
		}

		while ((0x8000 & GetAsyncKeyState((unsigned char)('\x20'))) == 0);
	}

	return 0;
}

一位数组存储像素点

在图像中以左上角为坐标原点,宽为x轴,高为y轴。如图所示,在一个3*3的图像中,可以用一个长度为9的一维数组arr的索引来表示像素点。索引与x,y的关系不难得知为

index=y*w+x

其中w表示图像的宽度。

代码流程

挑选蛇

为了方便操作,蛇的数据实现采用list。

struct sSnakeSegment {
	int x;
	int y;
};

list<sSnakeSegment> snake;

蛇移动

当蛇移动时,蛇的总长不变,使用push_front(),pop_back(),直接加头去尾。

void SnakeDir(void)
{
	switch (nSnakeDirection)
	{

	case 0: // Up
		snake.push_front({ snake.front().x, snake.front().y - step });
		break;

	case 1: // Right
		snake.push_front({ snake.front().x + step, snake.front().y });
		break;

	case 2:  // Down
		snake.push_front({ snake.front().x, snake.front().y + step });
		break;

	case 3:  // Left
		snake.push_front({ snake.front().x - step, snake.front().y });
		break;
	}


	snake.pop_back();
}

吃食物

当蛇头的x,y与食物的x,y相等时,就吃到食物。分数增加,把食物丢到一个空白的随机位置。

void eatFood()
{
	if (snake.front().x == nFoodX && snake.front().y == nFoodY) {
		nScore++;
		while (screen[nFoodY * nScreenWidth + nFoodX] != L' ') {
			nFoodX = rand() % nScreenWidth;
			nFoodY = (rand() % (nScreenHeight - 3)) + 3;
		}

		snake.push_back({ snake.back().x, snake.back().y });
	}

}

为什么食物的随机位置范围x在0到nScreenWidth(屏幕宽度),而y在3到(nScreenHeight-3)呢?

nFoodX = rand() % nScreenWidth;
nFoodY = (rand() % (nScreenHeight - 3)) + 3;

因为上面3行拿来显示分数了。

wsprintf(&screen[nScreenWidth * 3 / 2], L"SCORE: %d", nScore);

红框里的数字怎么出来的

在写代码时,我想把分数展示在1行的正中间,由index=y*w+x公式知,index=1*nScreenWidth+(nScreenWidth/2)=nScreenWidth*3/2。

死亡检测

要么撞墙,要么撞自己。

void DeadDetect(void)
{
	if (snake.front().x < 0 || snake.front().x >= nScreenWidth)
		bDead = true;
	if (snake.front().y < 3 || snake.front().y >= nScreenHeight)
		bDead = true;

	for (list<sSnakeSegment>::iterator i = snake.begin(); i != snake.end(); i++)
		if (i != snake.begin() && i->x == snake.front().x && i->y == snake.front().y)
			bDead = true;
}

更新屏幕

先把screen清空,然后在往字符数组screen里的特定位置写入字符。

void updateScreen(void)
{
	
	for (int i = 0; i < nScreenWidth * nScreenHeight; i++) screen[i] = L' ';
	for (int i = 0; i < nScreenWidth; i++) {
		screen[i] = L' ';
		screen[2 * nScreenWidth + i] = L'=';
	}
	wsprintf(&screen[nScreenWidth * 3 / 2], L"SCORE: %d", nScore);

	
	for (auto s : snake)
		screen[s.y * nScreenWidth + s.x] = bDead ? L'+' : L'O';

	
	screen[snake.front().y * nScreenWidth + snake.front().x] = bDead ? L'X' : L'@';

	
	screen[nFoodY * nScreenWidth + nFoodX] = L'%';

	if (bDead)
		wsprintf(&screen[15 * nScreenWidth + 40], L"    PRESS 'SPACE' TO PLAY AGAIN    ");
}

最难的代码

为什么要有缓冲区?因为这些显示到终端才不闪烁。反正就把下面的代码当成一个模板吧。

    //定义缓冲区
	HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
	SetConsoleActiveScreenBuffer(hConsole);
	DWORD dwBytesWritten = 0;



    //输出到终端
	WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值