目录
- 前言
- 一、 游戏总体框架
- 二、地图绘制
- 三、光标隐藏
- 四、地图定义
- 五、蛇体定义
- 六、蛇体绘制
- 七、蛇体移动
- 八、频率控制
- 九、边界检测
- 十、游戏失败
- 十一、蛇体转向
- 十二、食物生成
- 十三、食物碰撞
- 十四、整体代码
- 十五、结语
前言
各位小伙伴们好久不见,前段时间非常的忙很多事情,其中包括各种实验报告,各种课程设计,各种考试,所以很长一段时间没更新了,今天正好是放假的第一天,我们一起来学习贪吃蛇这个非常经典的项目实战。
一、 游戏总体框架
#include <iostream>
using namespace std;
int main() {
while (1) {
}
return 0;
}
二、地图绘制
#define H 28
#define W 60
void drawMap() {
system("cls");
cout << "┏";
for (int x = 0; x < W; ++x) {
cout << "━";
}
cout << "┓" << endl;
for (int y = 0; y < H; ++y) {
cout << "┃";
for (int x = 0; x < W; ++x) {
cout << " ";
}
cout << "┃" << endl;
}
cout << "┗";
for (int x = 0; x < W; ++x) {
cout << "━";
}
cout << "┛";
}
三、光标隐藏
void hideCursor() {
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO curInfo = { 1, FALSE };
SetConsoleCursorInfo(hOutput, &curInfo);
}
四、地图定义
enum BlockType {
EMPTY = 0,//没有食物就是空状态为0
FOOD = 1,//有食物的状态是1
};
struct Map {
BlockType data[H][W];//地图每个格子
bool hasFood;//判断地图有没有食物
};
void initMap(Map* map) {//初始化地图
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
map->data[y][x] = BlockType::EMPTY;//地图上每个格子都为空,也就是没有一个格子有食物为0
}
}
map->hasFood = false;//地图没有食物
}
五、蛇体定义
struct Snake {
Pos snake[H * W];
int snakeDir;//蛇的运动方向
int snakeLength;//蛇的长度
int lastMoveTime;
int moveFrequency;
};
void initSnake(Snake* snk) {//初始化蛇
snk->snakeLength = 3;//蛇刚开始的长度为3
snk->snakeDir = 2;//刚开始蛇的方向
snk->snake[0] = { W / 2,H / 2 };//蛇初始位置在地图中间
snk->snake[1] = { W / 2 - 1,H / 2 };
snk->snake[2] = { W / 2 - 2,H / 2 };
snk->lastMoveTime = 0;//上次移动的时间
snk->moveFrequency = 200;//移动的延时为200ms
}
六、蛇体绘制
void drawUnit(Pos p, const char unit[]) {//函数目的就是在特定位置打印字符串
COORD coord;//代表坐标
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
coord.X = p.x + 1;//因为方框占据一个位置,所以xy坐标加一
coord.Y = p.y + 1;
SetConsoleCursorPosition(hOutput, coord);
cout << unit;
}
void drawSnake(Snake* snk) {//绘制蛇身
for (int i = 0; i < snk->snakeLength; i++) {
drawUnit(snk->snake[i], "■");//蛇身用方块表示
}
}
七、蛇体移动
const int dir[4][2] = {//设运动的方向
{-1,0}, //上
{1,0}, //下
{0,-1}, //左
{0,1} //右
};
void moveSnake(Snake* snk) {
for (int i = snk->snakeLength - 1; i >= 1; --i) {
snk->snake[i] = snk->snake[i - 1];
}
snk->snake[0].y += dir[snk->snakeDir][0];
snk->snake[0].x += dir[snk->snakeDir][1];
}
bool doMove(Snake* snk, Map* map) {
Pos tail = snk->snake[snk->snakeLength - 1];
drawUnit(snk->snake[snk->snakeLength - 1], " ");
moveSnake(snk);
drawUnit(snk->snake[0], "■");
return true;
}
bool checkSnakeMove(Snake* snk, Map* map) {
doMove(snk, map);
return true;
}
八、频率控制
bool checkSnakeMove(Snake* snk, Map* map) {
int curTime = GetTickCount();
if (curTime - snk->lastMoveTime > snk->moveFrequency) {//延时处理
if (!doMove(snk, map)) return false;
snk->lastMoveTime = curTime;
}
return true;//蛇在移动返回true,没有移动返回false
}
九、边界检测
bool checkOutBound(Pos p) {//检测蛇是否超出边界,如果超出返回true,否则返回false
return (p.x <= 0 || p.x >= W + 1 || p.y <= 0 || p.y >= H + 1);
}
十、游戏失败
while (1) {//游戏主框架
checkChangeDir(&snk);
if (!checkSnakeMove(&snk, &map)) {
break;//如果蛇移动失败,终止游戏
}
checkFoodGenerate(&snk, &map);
}
drawUnit({ W / 2 - 4,H / 2 }, "Game Over");//结束时方框中间输出"Game Over"
while (1) {}
十一、蛇体转向
引入一个新的头文件:
#include <conio.h>
void checkChangeDir(Snake* snk) {//按键控制蛇方向
if (_kbhit()) {//判断键盘是否被接触
switch (_getch()) {
case'w': //w键
if (snk->snakeDir != 1)snk->snakeDir = 0;//方向为上方向再按反方向理论上行不通
break;
case'd': //d键
if (snk->snakeDir != 2)snk->snakeDir = 3;
break;
case's': //s键
if (snk->snakeDir != 0)snk->snakeDir = 1;
break;
case'a': //a键
if (snk->snakeDir != 3)snk->snakeDir = 2;
break;
default:
break;
}
}
}
十二、食物生成
void checkFoodGenerate(Snake* snk, Map* map) {//生成食物
if (!map->hasFood) {
while (1) {
int x = rand() % W;//随机生成食物的x,y坐标
int y = rand() % H;
int i = 0;
while (i < snk->snakeLength) {//生成食物的位置不能在蛇身上
if (x == snk->snake[i].x && y == snk->snake[i].y) {//判断食物位置是否在蛇身上
break;
}
i++;
}
if (i == snk->snakeLength) {
map->data[y][x] = BlockType::FOOD;
map->hasFood = true;
drawUnit({ x,y }, "●");//食物用●表示
return;
}
}
}
}
十三、食物碰撞
void checkEatFood(Snake* snk, Pos tail, Map* map) {//判断蛇头有没有撞到食物,撞到蛇身就变长
Pos head = snk->snake[0];//蛇头
if (map->data[head.y][head.x] == BlockType::FOOD) {
snk->snake[snk->snakeLength++] = tail;//蛇身加一,改变蛇尾位置
map->data[head.y][head.x] = BlockType::EMPTY;//食物位置变为空
map->hasFood = false;//地图没有食物了
drawUnit(tail, "■");//新蛇尾画出■
}
}
十四、整体代码
#include<iostream>
#include<Windows.h>
#include<conio.h>
using namespace std;
#define H 27 //地图长度
#define W 60 //地图宽度
const int dir[4][2] = {//设运动的方向
{-1,0}, //上
{1,0}, //下
{0,-1}, //左
{0,1} //右
};
enum BlockType {
EMPTY = 0,//没有食物就是空状态为0
FOOD = 1,//有食物的状态是1
};
struct Map {
BlockType data[H][W];//地图每个格子
bool hasFood;//判断地图有没有食物
};
struct Pos {
int x;
int y;
};
struct Snake {
Pos snake[H * W];
int snakeDir;//蛇的运动方向
int snakeLength;//蛇的长度
int lastMoveTime;
int moveFrequency;
};
void initSnake(Snake* snk) {//初始化蛇
snk->snakeLength = 3;//蛇刚开始的长度为3
snk->snakeDir = 2;//刚开始蛇的方向
snk->snake[0] = { W / 2,H / 2 };//蛇初始位置在地图中间
snk->snake[1] = { W / 2 - 1,H / 2 };
snk->snake[2] = { W / 2 - 2,H / 2 };
snk->lastMoveTime = 0;//上次移动的时间
snk->moveFrequency = 200;//移动的延时为200ms
}
void hideCursor() {//隐藏光标,隐藏光标没有鸟用就是看起来舒服点
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//将输出窗口编号存起来
CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };
SetConsoleCursorInfo(hOutput, &curInfo);//把窗口对应的光标隐藏
}
void initMap(Map* map) {//初始化地图
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
map->data[y][x] = BlockType::EMPTY;//地图上每个格子都为空,也就是没有一个格子有食物为0
}
}
map->hasFood = false;//地图没有食物
}
void drawUnit(Pos p, const char unit[]) {//函数目的就是在特定位置打印字符串
COORD coord;//代表坐标
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
coord.X = p.x + 1;//因为方框占据一个位置,所以xy坐标加一
coord.Y = p.y + 1;
SetConsoleCursorPosition(hOutput, coord);
cout << unit;
}
void drawMap(Map* map) {//制作地图
system("cls");//将控制台清空
cout << "┏";//上框边
for (int i = 0; i < W; i++) {
cout << "━";
}
cout << "┓" << endl;
for (int y = 0; y < H; y++) {
cout << "┃";
for (int x = 0; x < W; x++) {//左右框边
if (map->data[y][x] == BlockType::EMPTY) cout << " ";
}
cout << "┃" << endl;
}
cout << "┗";//下框边
for (int i = 0; i < W; i++) {
cout << "━";
}
cout << "┛" << endl;
}
void drawSnake(Snake* snk) {//绘制蛇身
for (int i = 0; i < snk->snakeLength; i++) {
drawUnit(snk->snake[i], "■");//蛇身用方块表示
}
}
bool checkOutBound(Pos p) {//检测蛇是否超出边界,如果超出返回true,否则返回false
return (p.x <= 0 || p.x >= W + 1 || p.y <= 0 || p.y >= H + 1);
}
void checkEatFood(Snake* snk, Pos tail, Map* map) {//判断蛇头有没有撞到食物,撞到蛇身就变长
Pos head = snk->snake[0];//蛇头
if (map->data[head.y][head.x] == BlockType::FOOD) {
snk->snake[snk->snakeLength++] = tail;//蛇身加一,改变蛇尾位置
map->data[head.y][head.x] = BlockType::EMPTY;//食物位置变为空
map->hasFood = false;//地图没有食物了
drawUnit(tail, "■");//新蛇尾画出■
}
}
void moveSnake(Snake* snk) {
for (int i = snk->snakeLength - 1; i >= 1; i--) {
snk->snake[i] = snk->snake[i - 1];//贪吃蛇的核心
} //因为蛇身是往前走的,每个蛇身现在的位置变成它前一个蛇身的位置
snk->snake[0].y += dir[snk->snakeDir][0];//改变蛇头的位置
snk->snake[0].x += dir[snk->snakeDir][1];
}
bool doMove(Snake* snk, Map* map) {//蛇体移动
Pos tail = snk->snake[snk->snakeLength - 1];//拿到蛇的尾部
drawUnit(tail, " "); //因为蛇再往前走,所以蛇尾原来的位置就为空的
moveSnake(snk);
if (checkOutBound(snk->snake[0])) {//蛇移动完以后判断是否超出边界,如果超出停止运动
return false;
}
checkEatFood(snk, tail, map);
drawUnit(snk->snake[0], "■");//蛇往前走,蛇头的前一个位置要画一个方块
return true; //如果移动到边界的时候返回false,也就是会移动失败
}
bool checkSnakeMove(Snake* snk, Map* map) {
int curTime = GetTickCount();
if (curTime - snk->lastMoveTime > snk->moveFrequency) {//延时处理
if (!doMove(snk, map)) return false;
snk->lastMoveTime = curTime;
}
return true;//蛇在移动返回true,没有移动返回false
}
void checkChangeDir(Snake* snk) {//按键控制蛇方向
if (_kbhit()) {//判断键盘是否被接触
switch (_getch()) {
case'w': //w键
if (snk->snakeDir != 1)snk->snakeDir = 0;//方向为上方向再按反方向理论上行不通
break;
case'd': //d键
if (snk->snakeDir != 2)snk->snakeDir = 3;
break;
case's': //s键
if (snk->snakeDir != 0)snk->snakeDir = 1;
break;
case'a': //a键
if (snk->snakeDir != 3)snk->snakeDir = 2;
break;
default:
break;
}
}
}
void checkFoodGenerate(Snake* snk, Map* map) {//生成食物
if (!map->hasFood) {
while (1) {
int x = rand() % W;//随机生成食物的x,y坐标
int y = rand() % H;
int i = 0;
while (i < snk->snakeLength) {//生成食物的位置不能在蛇身上
if (x == snk->snake[i].x && y == snk->snake[i].y) {//判断食物位置是否在蛇身上
break;
}
i++;
}
if (i == snk->snakeLength) {
map->data[y][x] = BlockType::FOOD;
map->hasFood = true;
drawUnit({ x,y }, "●");//食物用●表示
return;
}
}
}
}
void initGame(Snake* snk, Map* map) {//游戏调用的函数
hideCursor();//去除光标
initMap(map);//初始化地图
initSnake(snk);//初始化蛇
drawMap(map);//画图
drawSnake(snk);//画蛇
}
int main() {
Map map;
Snake snk;
initGame(&snk, &map);
while (1) {//游戏主框架
checkChangeDir(&snk);
if (!checkSnakeMove(&snk, &map)) {
break;//如果蛇移动失败,终止游戏
}
checkFoodGenerate(&snk, &map);
}
drawUnit({ W / 2 - 4,H / 2 }, "Game Over");//结束时方框中间输出"Game Over"
while (1) {}
return 0;
}
十五、结语
建议大家自己手敲一遍,还有就是我写了这么多注释是为了方便大家理解,你们在写代码的时候记得不要写那么多注释,使得代码不美观,而且增加许多与项目无关的文字很有可能导致项目调试的时候出现bug。
(在这里我祝大家2025年新年快乐,非常感谢大家的观看,希望大家点赞加关注支持一下,谢谢大家!!!)