#C++ Day22 November 4 2025

//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;

}