//snake game
#include <iostream>
using namespace std;
int main() {
int cnt = 0;
while (1) { //basic structure
++cnt;
cout << “The game is running……” <<cnt <<endl;
}
return 0;
}
//07蛇体绘制
#include <iostream>
#include <Windows.h>
using namespace std;
#define H 27 //is mean 27 lines
#define W 60
//if we met the Height or width,we are using these substitutes.
enum BlockType {//用枚举类型来定义每个格子是什么类型 自定义类型
EMPTY = 0,//空
FOOD = 1,//食物
};
struct Map {
BlockType data[H][W];//H行 W列的矩阵 二维数组
bool hasFood;//定义这张地图上有没有食物
};
struct Pos { //蛇的位置 结构体
int x;
int y;
};
struct Snake {
Pos snake[H * W]; //位置 把蛇表示在一维数组里面
int snakeDir;//蛇的方向
int snakeLength;//蛇的长度
};
void initSnake(Snake* snk) {//为什么传指针 不传结构体本身 因为我要在函数内部修改值 并且在函数外部也改掉 必须用到指针
snk->snakeLength = 3; //蛇的长度
snk->snakeDir = 1; //蛇的方向
snk->snake[0] = { W / 2,H / 2 };//蛇的位置放到屏幕中间 W是x H是y 结构体初始化
snk->snake[1] = { W / 2 – 1,H / 2 };
snk->snake[2] = { W / 2 – 2,H / 2 };
}
void hideCursor() {
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //输出窗口句柄,句柄就是编号
CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };//网上一搜索就知道这个结构体,代表隐藏掉它
SetConsoleCursorInfo(hOutput, &curInfo);//传递cursor的地址,结构体指针
}
void initMap(Map* map) {//传递Map的指针 为了它这个结构体能够被修改
for (int y = 0; y < H; ++y) {//遍历行
for (int x = 0; x < W; ++x)//遍历列 先遍历哪个都行
map->data[y][x] = BlockType::EMPTY; //枚举 等价于 map->data[y][x] = 0;
}
map->hasFood = false;//第一层 没有食物
}
void drawUnit(Pos p, const char unit[]) {
COORD coord;//结构体 代表坐标
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//拿到窗口句柄
coord.X = p.x + 1;//因为地图上下左右 都包了一圈 整体会有一的偏移量
coord.Y = p.y + 1;
SetConsoleCursorPosition(hOutput, coord); //设置光标位置的接口 传窗口句柄(哪个窗口) 和 位置变量结构体
cout << unit;//打印传进来的字符串变量
}
void drawMap(Map* map) { //传参
//cout << “123” << endl;//test the output
system(“cls”); // calling the command prompt for clearing the terminal
cout << “┏”; //⌈¯⌉ ⌊_⌋ upper border
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 << “┗”; // lowest border
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], “■”); //蛇身长度 和 用什么来表示这条蛇
}
}
int main() {
Map map;//结构体
Snake snk;
hideCursor();//隐藏光标
initMap(&map);//结构体传地址 变成结构体指针
initSnake(&snk);
drawMap(&map);//绘制地图 结构体传地址 变成结构体指针
drawSnake(&snk);//绘制完地图后 把蛇传进来
while (1) {//basic structure
}
return 0;
}
//08蛇体移动
这段代码实现了贪吃蛇移动时身体部分的核心逻辑,我们来分析一下它的工作原理:
这段代码的作用是让蛇的身体部分跟随前一段移动。假设snk->snake是一个存储蛇身体各段坐标的数组(通常snake[0]是蛇头,snake[1]是蛇头后面的第一节身体,以此类推),snk->snakeLength是蛇的总长度。
循环从蛇的最后一段(i = snk->snakeLength – 1)开始,一直到第二节身体(i >= 1),将每一段的位置更新为前一段的位置(snk->snake[i] = snk->snake[i – 1])。
举个例子:如果蛇有 3 段(长度为 3),索引为 0(头)、1(中间)、2(尾),那么循环会:
- 先处理 i=2(尾部),让尾部移动到 i=1(中间)的位置
- 再处理 i=1(中间),让中间移动到 i=0(头部)的位置
这样就完成了整个身体的跟随移动。需要注意的是,这段代码只处理了身体部分,蛇头的移动需要在调用这个函数之后,根据当前方向(上 / 下 / 左 / 右)单独更新snake[0]的坐标。
这种实现方式非常经典,通过 “后一段复制前一段位置” 的逻辑,高效地实现了蛇的整体移动效果。
//08蛇体移动
#include <iostream>
#include <Windows.h>
using namespace std;
#define H 27 //is mean 27 lines
#define W 60
//if we met the Height or width,we are using these substitutes.
const int dir[4][2] = { //让蛇动起来 上下左右来说 x,y方向 二维数组
{-1,0},//上 y方向-1 x方向不变
{0,1},//右 → y方向不变 x+1
{1,0},//下
{0,-1}//左
};
enum BlockType {//用枚举类型来定义每个格子是什么类型 自定义类型
EMPTY = 0,//空
FOOD = 1,//食物
};
struct Map {
BlockType data[H][W];//H行 W列的矩阵 二维数组
bool hasFood;//定义这张地图上有没有食物
};
struct Pos { //蛇的位置 结构体
int x;
int y;
};
struct Snake {
Pos snake[H * W]; //位置 把蛇表示在一维数组里面
int snakeDir;//蛇的方向
int snakeLength;//蛇的长度
};
void initSnake(Snake* snk) {//为什么传指针 不传结构体本身 因为我要在函数内部修改值 并且在函数外部也改掉 必须用到指针
snk->snakeLength = 3; //蛇的长度
snk->snakeDir = 1; //蛇的方向
snk->snake[0] = { W / 2,H / 2 };//蛇的位置放到屏幕中间 W是x H是y 结构体初始化
snk->snake[1] = { W / 2 – 1,H / 2 };
snk->snake[2] = { W / 2 – 2,H / 2 };
}
void hideCursor() {
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //输出窗口句柄,句柄就是编号
CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };//网上一搜索就知道这个结构体,代表隐藏掉它
SetConsoleCursorInfo(hOutput, &curInfo);//传递cursor的地址,结构体指针
}
void initMap(Map* map) {//传递Map的指针 为了它这个结构体能够被修改
for (int y = 0; y < H; ++y) {//遍历行
for (int x = 0; x < W; ++x)//遍历列 先遍历哪个都行
map->data[y][x] = BlockType::EMPTY; //枚举 等价于 map->data[y][x] = 0;
}
map->hasFood = false;//第一层 没有食物
}
void drawUnit(Pos p, const char unit[]) {
COORD coord;//结构体 代表坐标
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//拿到窗口句柄
coord.X = p.x + 1;//因为地图上下左右 都包了一圈 整体会有一的偏移量
coord.Y = p.y + 1;
SetConsoleCursorPosition(hOutput, coord); //设置光标位置的接口 传窗口句柄(哪个窗口) 和 位置变量结构体
cout << unit;//打印传进来的字符串变量
}
void drawMap(Map* map) { //传参
//cout << “123” << endl;//test the output
system(“cls”); // calling the command prompt for clearing the terminal
cout << “┏”; //⌈¯⌉ ⌊_⌋ upper border
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 << “┗”; // lowest border
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], “■”); //蛇身长度 和 用什么来表示这条蛇
}
}
// (0,0) , (0,1) , (0,2)
// 头 中 尾
//往(1,0)方向走
// (0+1,0+0) , (0,0) , (0,1)
// 头 中 尾
void moveSnake(Snake* snk) {
for (int i = snk->snakeLength – 1; i >= 1; –i) {
snk->snake[i] = snk->snake[i – 1];
}//整个贪吃蛇的核心
//时间复杂度高 每次O(n)
snk->snake[0].y += dir[snk->snakeDir][0];//蛇头加上当前的方向
snk->snake[0].x += dir[snk->snakeDir][1];
}
void doMove(Snake* snk, Map* map) {
Pos tail = snk->snake[snk->snakeLength – 1];//蛇的尾部位置,因为蛇的位置从0开始
drawUnit(tail, ” “);//蛇尾部擦掉
moveSnake(snk);
drawUnit(snk->snake[0], “■”);//在蛇头的位置画一个新的方格出来
}
void checkSnakeMove(Snake* snk, Map* map) { //蛇和地图 结构体指针
doMove(snk, map);
}
int main() {
Map map;//结构体
Snake snk;
hideCursor();//隐藏光标
initMap(&map);//结构体传地址 变成结构体指针
initSnake(&snk);
drawMap(&map);//绘制地图 结构体传地址 变成结构体指针
drawSnake(&snk);//绘制完地图后 把蛇传进来
while (1) {//basic structure
checkSnakeMove(&snk, &map);
}
return 0;
}
//完成版
#include <iostream>
#include <Windows.h>
#include <conio.h> //为了检测按键有没有被按下
using namespace std;
//每个函数代码不要太长 要简单能看懂
#define H 27 //is mean 27 lines
#define W 60
//if we met the Height or width,we are using these substitutes.
const int dir[4][2] = { //让蛇动起来 上下左右来说 x,y方向 二维数组
{-1,0},//上 y方向-1 x方向不变
{0,1},//右 → y方向不变 x+1
{1,0},//下
{0,-1}//左
};
enum BlockType {//用枚举类型来定义每个格子是什么类型 自定义类型
EMPTY = 0,//空
FOOD = 1,//食物
};
struct Map {
BlockType data[H][W];//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; //蛇的长度
snk->snakeDir = 1; //蛇的方向
snk->snake[0] = { W / 2,H / 2 };//蛇的位置放到屏幕中间 W是x H是y 结构体初始化
snk->snake[1] = { W / 2 – 1,H / 2 };
snk->snake[2] = { W / 2 – 2,H / 2 };
snk->lastMoveTime = 0;
snk->moveFrequency = 200;
}
void hideCursor() {
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //输出窗口句柄,句柄就是编号
CONSOLE_CURSOR_INFO curInfo = { 1,FALSE };//网上一搜索就知道这个结构体,代表隐藏掉它
SetConsoleCursorInfo(hOutput, &curInfo);//传递cursor的地址,结构体指针
}
void initMap(Map* map) {//传递Map的指针 为了它这个结构体能够被修改
for (int y = 0; y < H; ++y) {//遍历行
for (int x = 0; x < W; ++x)//遍历列 先遍历哪个都行
map->data[y][x] = BlockType::EMPTY; //枚举 等价于 map->data[y][x] = 0;
}
map->hasFood = false;//第一层 没有食物
}
void drawUnit(Pos p, const char unit[]) {
COORD coord;//结构体 代表坐标
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//拿到窗口句柄
coord.X = p.x + 1;//因为地图上下左右 都包了一圈 整体会有一的偏移量
coord.Y = p.y + 1;
SetConsoleCursorPosition(hOutput, coord); //设置光标位置的接口 传窗口句柄(哪个窗口) 和 位置变量结构体
cout << unit;//打印传进来的字符串变量
}
void drawMap(Map* map) { //传参
//cout << “123” << endl;//test the output
system(“cls”); // calling the command prompt for clearing the terminal
cout << “┏”; //⌈¯⌉ ⌊_⌋ upper border
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 << “┗”; // lowest border
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 checkOutOfBound(Pos p) { //边界检测
/*
if (p.x == 0 || p.x == W + 1) { //逻辑或运算符
return true;//说明超出了横向边界
}
if (p.y == 0 || p.y == H + 1) {
return true;//说明超出了纵向边界
}
return 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;//当他变成false下次检测时就会生成新的出来
drawUnit(tail, “■”);//在尾部放上一个方格
}
}
// (0,0) , (0,1) , (0,2)
// 头 中 尾
//往(1,0)方向走
// (0+1,0+0) , (0,0) , (0,1)
// 头 中 尾
void moveSnake(Snake* snk) {
for (int i = snk->snakeLength – 1; i >= 1; –i) {
snk->snake[i] = snk->snake[i – 1];
}//整个贪吃蛇的核心
//时间复杂度高 每次O(n)
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];//蛇的尾部位置,因为蛇的位置从0开始
drawUnit(tail, ” “);//蛇尾部擦掉
moveSnake(snk);
//在这条蛇移动之后检测,把蛇头的位置传进去
if (checkOutOfBound(snk->snake[0])) {
return false;//如果超出边界 就return false
}
checkEatFood(snk, tail, map);
drawUnit(snk->snake[0], “■”);//在蛇头的位置画一个新的方格出来
return true;//没有超出边界 就return true
}
bool checkSnakeMove(Snake* snk, Map* map) { //蛇和地图 结构体指针
//如果上次刚移动过 我就不移动了
int curTime = GetTickCount(); //当前时间
if (curTime – snk->lastMoveTime > snk->moveFrequency) {
if(false == doMove(snk, map))//边界检测
return false;//当前时间-上次移动时间大于移动频率 就执行移动操作
snk->lastMoveTime = curTime;//上次移动时间设置为当前时间
}
return true;
}
void checkChangeDir(Snake * snk) {//检测方向旋转
if(_kbhit()){ //如果键盘被按下
switch (_getch()) {
case ‘w’:
if(snk->snakeDir!=2)//不能同时按上下
snk->snakeDir = 0;//把蛇的方向变成上方向 0
break;
case ‘d’:
if (snk->snakeDir != 3)//不能同时按左右
snk->snakeDir = 1;//把蛇的方向变成右方向 1
break;
case ‘s’:
if (snk->snakeDir != 0)//不能同时按上下
snk->snakeDir = 2;//把蛇的方向变成下方向 2
break;
case ‘a’:
if (snk->snakeDir != 1)//不能同时按左右
snk->snakeDir = 3;//把蛇的方向变成左方向 3
break;
default://其他按键不做任何处理
break;
}
}
}
void checkFoodGenerate(Snake* snk, Map* map) {
if (false == map->hasFood) {
while (1) {
int x = rand() % W;//生成的食物不能落在蛇身上
int y = rand() % H; //0~H-1
int i = 0;
while (i < snk->snakeLength) {
if (x == snk->snake[i].x && y == snk->snake[i].y) {//身体的第i个位置的x和y坐标
break;
}
i++;//需要自增来遍历蛇身长度
}
if (i == snk->snakeLength) {
map->data[y][x] = BlockType::FOOD;
map->hasFood = true;
drawUnit({ x,y }, “●”);
return; //只要位置生成了就要return出去
//break;//或者这样
}
}
}
}
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) {//basic structure
checkChangeDir(&snk);
if (false == checkSnakeMove(&snk, &map)) {
break;//如果发现这条蛇移动到失败 就终止游戏
}
checkFoodGenerate(&snk, &map);
}
drawUnit({ W / 2 – 4,H / 2 }, “Game Over”);
while(1){}//结束不让它结束
return 0;
}