来源
源代码链接
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的关系不难得知为
其中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=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);