一、控制台相关设置(直接复制)
1、右击控制台属性,布局。调整控制台宽度120,高度40。
2、设置工程属性,字符集Unicode。
#include<iostream> using namespace std; #include<Windows.h> int nScreenWidth = 120; int nScreenHeight = 40; int main() { //创建屏幕缓冲区 wchar_t * screen = new wchar_t[nScreenWidth*nScreenHeight]; 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) { //显示 WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth*nScreenHeight, { 0,0 }, &dwBytesWritten); } return 0; }
二、游戏部分
2.1 蛇的数据结构
使用 x,y 表示蛇的一节身体, 使用列表存储 ,使用整形变量nSnakeDirection 表示蛇运动方向。
struct sSnakeSegment { int x; int y; }; //使用列表定义蛇,并初始化 list<sSnakeSegment> snake = { {60,15},{61,15},{62,15},{63,15},{64,15},{65,15},{66,15},{67,15},{68,15},{69,15} }; int nSnakeDirection = 3; //上0 右1 下2 左3 (顺时针)
2.2 绘制蛇 、食物
//清屏 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 + 5], L"www.OneLoneCoder.com - S N A K E ! ! SCORE: %d", nScore); //绘制蛇的身体 for (auto s : snake) screen[s.y*nScreenWidth + s.x] = bDead ? L'+' : L'O'; //'O'表示蛇的身体,当游戏结束时,身体变成+ //绘制蛇头 screen[snake.front().y*nScreenWidth + snake.front().x] = bDead ? L'X' : L'@'; //绘制食物 screen[nFoodY*nScreenWidth + nFoodX] = L'%';
2.3 处理用户输入
//计时 & 输入 this_thread::sleep_for(200ms); // Get Input, bKeyRight = (0x8000 & GetAsyncKeyState((unsigned char)('\x27'))) != 0; bKeyLeft = (0x8000 & GetAsyncKeyState((unsigned char)('\x25'))) != 0; if (bKeyRight && !bKeyRightOld) { nSnakeDirection++; if (nSnakeDirection == 4) nSnakeDirection = 0; } if (bKeyLeft && !bKeyLeftOld) { nSnakeDirection--; if (nSnakeDirection == -1) nSnakeDirection = 3; }
2.4 更新蛇的位置
//更新蛇的位置 switch (nSnakeDirection) { case 0: //上 snake.push_front({ snake.front().x,snake.front().y - 1 }); break; case 1: //右 snake.push_front({ snake.front().x+1,snake.front().y }); break; case 2: //下 snake.push_front({ snake.front().x,snake.front().y +1 }); break; case 3: //左 snake.push_front({ snake.front().x-1,snake.front().y }); break; } //删除最后一个(蛇尾) snake.pop_back(); //碰撞检测 if (snake.front().x < 0 || snake.front().x >= nScreenWidth) bDead = true; if (snake.front().y < 3 || snake.front().y >= nScreenHeight) bDead = true;
2.5 碰撞检测
//边界碰撞检测 if (snake.front().x < 0 || snake.front().x >= nScreenWidth) bDead = true; if (snake.front().y < 3 || snake.front().y >= nScreenHeight) bDead = true; //食物碰撞检测 if (snake.front().x == nFoodX && snake.front().y == nFoodY) { nScore++; while (screen[nFoodY*nScreenWidth + nFoodX] != ' ') //重新生成食物 { nFoodX = rand() % nScreenWidth; nFoodY = (rand() % (nScreenHeight - 3)) + 3; } for (int i = 0; i < 5; i++) snake.push_back({ snake.back().x,snake.back().y }); } // 蛇身体碰撞检测 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;
2.6 游戏状态(死亡后重新开始)
当蛇死亡时,游戏更新的循环退出,等待按键空格
while(1) { while (!bDead) { //游戏更新 } //等待空格重新开始 while ((0x8000 & GetAsyncKeyState((unsigned char)('\x20'))) == 0); }
2.7 其他优化
取消硬延迟,调整上下,左右的延迟
// Get Input auto t1 = chrono::system_clock::now(); //由于控制台横高宽度不同,这里让左右延迟120ms,上下200ms while ((chrono::system_clock::now() - t1) < ((nSnakeDirection % 2 == 1) ? 120ms : 200ms)) { bKeyRight = (0x8000 & GetAsyncKeyState((unsigned char)('\x27'))) != 0; bKeyLeft = (0x8000 & GetAsyncKeyState((unsigned char)('\x25'))) != 0; }
三、完整代码
#include<iostream> #include<list> #include <thread> #include<chrono> using namespace std; #include<Windows.h> int nScreenWidth = 120; int nScreenHeight = 40; struct sSnakeSegment { int x; int y; }; int main() { //创建屏幕缓冲区 wchar_t * screen = new wchar_t[nScreenWidth*nScreenHeight]; 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) { //使用列表定义蛇,并初始化 list<sSnakeSegment> snake = { {60,15},{61,15},{62,15},{63,15},{64,15},{65,15},{66,15},{67,15},{68,15},{69,15} }; int nFoodX = 30; int nFoodY = 15; int nScore = 0; int nSnakeDirection = 3; //上0 右1 下2 左3 (顺时针) bool bDead = false; bool bKeyLeft = false, bKeyRight = false, bKeyLeftOld = false, bKeyRightOld = false; while (!bDead) { //计时 & 输入 //this_thread::sleep_for(200ms); // Get Input auto t1 = chrono::system_clock::now(); //由于控制台横高宽度不同,这里让左右延迟120ms,上下200ms while ((chrono::system_clock::now() - t1) < ((nSnakeDirection % 2 == 1) ? 120ms : 200ms)) { bKeyRight = (0x8000 & GetAsyncKeyState((unsigned char)('\x27'))) != 0; bKeyLeft = (0x8000 & GetAsyncKeyState((unsigned char)('\x25'))) != 0; } if (bKeyRight && !bKeyRightOld) { nSnakeDirection++; if (nSnakeDirection == 4) nSnakeDirection = 0; } if (bKeyLeft && !bKeyLeftOld) { nSnakeDirection--; if (nSnakeDirection == -1) nSnakeDirection = 3; } //游戏逻辑 //更新蛇的位置 switch (nSnakeDirection) { case 0: //上 snake.push_front({ snake.front().x,snake.front().y - 1 }); break; case 1: //右 snake.push_front({ snake.front().x + 1,snake.front().y }); break; case 2: //下 snake.push_front({ snake.front().x,snake.front().y + 1 }); break; case 3: //左 snake.push_front({ snake.front().x - 1,snake.front().y }); break; } //边界碰撞检测 if (snake.front().x < 0 || snake.front().x >= nScreenWidth) bDead = true; if (snake.front().y < 3 || snake.front().y >= nScreenHeight) bDead = true; //食物碰撞检测 if (snake.front().x == nFoodX && snake.front().y == nFoodY) { nScore++; while (screen[nFoodY*nScreenWidth + nFoodX] != ' ') //重新生成食物 { nFoodX = rand() % nScreenWidth; nFoodY = (rand() % (nScreenHeight - 3)) + 3; } for (int i = 0; i < 5; i++) snake.push_back({ snake.back().x,snake.back().y }); } // 蛇身体碰撞检测 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; //删除最后一个(蛇尾) snake.pop_back(); //显示 //清屏 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 + 5], L"www.OneLoneCoder.com - S N A K E ! ! SCORE: %d", nScore); //绘制蛇的身体 for (auto s : snake) screen[s.y*nScreenWidth + s.x] = bDead ? L'+' : L'O'; //'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 "); WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth*nScreenHeight, { 0,0 }, &dwBytesWritten); } while ((0x8000 & GetAsyncKeyState((unsigned char)('\x20'))) == 0); } return 0; }