C/C++控制台贪吃蛇游戏的实现

🚀欢迎互三👉:程序猿方梓燚 💎💎

🚀关注博主,后期持续更新系列文章

🚀如果有错误感谢请大家批评指出,及时修改

🚀感谢大家点赞👍收藏⭐评论✍

一、概述

本文对给定的贪吃蛇游戏代码进行详细分析。该游戏使用 C++语言编写,通过控制台界面实现了经典的贪吃蛇游戏玩法,包括登录、注册、游戏介绍、游戏操作和计分等功能。

二、功能模块分析

(一)基础模块

常量定义:
MAX:定义了蛇身最大长度为 100
UPDOWNLEFTRIGHT:分别代表蛇的上、下、左、右四个方向。
MOVING:表示蛇正在移动的状态。
STOP:表示蛇停止的状态。
全局变量:
hMain_Out:控制台输出句柄。
hMain_In:控制台输入句柄。
NewPos[MAX]:用于存储蛇身新位置的数组。
Food:食物的位置结构体。
Wall:墙壁的范围结构体。
countgradelevelamountspeed:分别用于记录蛇的移动步数、分数、难度等级、食物数量和移动速度。
isPaused:表示游戏是否暂停的布尔变量。
基础函数:
HideTheCursor():隐藏光标。通过获取控制台光标信息,将其可见性设置为 FALSE,实现隐藏光标的效果。
basic():游戏的基础功能模块,包括菜单选择(登录、注册、游戏介绍、退出),根据用户选择调用相应的函数。
显示菜单选项,让用户选择登录、注册、游戏介绍或退出。
根据用户选择调用相应的函数,如login()registerUser()gameIntroduction()out()
out():退出游戏,显示感谢信息并逐步退出。
显示感谢信息和退出提示。
使用循环和延迟来模拟逐步退出的效果。
login():实现用户登录功能,检查用户名和密码是否正确。
提示用户输入用户名和密码。
检查输入是否为空,如果为空则显示错误信息和提示框。
读取用户信息文件,对比输入的用户名和密码是否与文件中的一致。
registerUser():用户注册功能,将新用户的用户名和密码保存到文件中。
提示用户输入新用户名和密码。
检查输入是否为空,如果为空则显示错误信息和提示框。
打开用户信息文件,将新用户名和密码写入文件。
gameIntroduction():展示游戏介绍界面,介绍游戏规则,一段时间后自动返回大厅。
显示游戏介绍信息和规则说明。
使用循环和延迟来模拟自动返回大厅的效果。

(二)游戏模块

初始化函数:
Init(Body& b):初始化蛇的初始位置、长度、方向等,设置控制台输出句柄,创建游戏墙壁和食物,并显示游戏信息。
设置蛇的初始长度为 3,初始方向为向右。
获取控制台输出句柄和输入句柄。
创建游戏墙壁,通过获取控制台屏幕缓冲区信息,确定墙壁的范围,并在边界绘制墙壁。
随机生成食物的位置,确保食物位置在有效范围内且坐标为偶数。
显示游戏信息,包括分数和难度等级。
输出函数:
Print(const Body& b):在控制台输出蛇的位置,以圆形符号 "●" 表示蛇身。
使用循环遍历蛇身位置数组,设置控制台光标位置,输出蛇身符号。
Print(int x, int y):在指定坐标位置输出特定字符,用于创建墙壁和食物。
设置控制台光标位置,输出指定字符。
移动函数:
Move(Body& b):实现蛇的移动逻辑,包括判断是否碰到墙壁或食物,更新蛇的位置,增加蛇身长度等。如果蛇碰到墙壁,则显示游戏结束信息并重新开始游戏;如果碰到食物,则增加分数、蛇身长度和食物数量,重新生成食物。
显示游戏信息,包括分数和难度等级。
判断蛇是否碰到墙壁,如果碰到墙壁则显示游戏结束信息并重新开始游戏。
判断蛇是否碰到食物,如果碰到食物则增加分数、蛇身长度和食物数量,清除食物位置并重新生成食物。
根据蛇的状态和方向更新蛇的位置。如果蛇处于停止状态,根据方向直接移动蛇身;如果蛇处于移动状态,根据方向逐步移动蛇身。
输出蛇的新位置。
GetDirection(Body& b):根据用户按键输入获取蛇的移动方向。
判断用户是否按下上、下、左、右方向键,如果按下则改变蛇的方向。
TurnRound(int d, Body& b):根据给定方向改变蛇的移动方向。
根据给定方向和蛇的当前方向,判断是否可以改变方向。如果可以改变方向,则复制蛇的当前位置到临时数组,更新蛇头位置,设置蛇的新方向和移动状态。
PosCopy(Body& b, Pos NewPos[]):复制蛇的当前位置到一个临时数组中。
遍历蛇身位置数组,将每个位置复制到临时数组中。
MoveBody(Body& b):更新蛇身的位置,根据临时数组中的位置信息进行移动。
遍历蛇身位置数组,从蛇尾开始,将每个位置更新为前一个位置的值。最后,增加移动步数计数,并复制蛇的当前位置到临时数组。
辅助函数:
Clean(int x, int y):清除指定坐标位置的字符,用于移动蛇身时清除旧位置的显示。
设置控制台光标位置,输出空格字符,清除指定位置的显示。
HideCursor():隐藏控制台光标。通过获取控制台光标信息,将其可见性设置为 FALSE,实现隐藏光标的效果。
CreateWall():创建游戏墙壁,在控制台边界绘制墙壁。
获取控制台屏幕缓冲区信息,确定墙壁的范围。
使用循环在墙壁边界输出特定字符,绘制墙壁。
CreateFood():随机生成食物的位置,并在控制台输出食物。
使用随机数生成器和时间种子生成随机坐标。
确保食物位置在有效范围内且坐标为偶数。
在指定位置输出食物符号。
IsKnock_Food(const Body& b):判断蛇是否碰到食物。
比较蛇头的位置和食物的位置,如果相同则返回 true,否则返回 false
IsKnock_Wall(const Body& b):判断蛇是否碰到墙壁或自身。
检查蛇头的位置是否在墙壁范围内或与自身其他部分重叠,如果是则返回 true,否则返回 false
ShowInfo():在控制台显示游戏信息,包括分数和难度等级。
设置控制台光标位置,输出分数和难度等级信息。
AddBody(Body& b):增加蛇的长度,根据蛇的当前方向在蛇尾添加一个新的位置。
根据蛇的当前方向,在蛇尾添加一个新的位置,并增加蛇的长度。

(三)主函数

main()函数作为程序的入口点,设置控制台模式和标题,调用basic()函数进入游戏的基础功能模块。

三、代码结构分析

代码结构清晰,通过多个函数实现不同的功能模块,易于理解和维护。
使用结构体Body来表示蛇的状态,包括位置、长度、方向和状态等信息,方便对蛇进行操作和管理。
利用全局变量来存储游戏中的一些状态信息,如分数、等级、食物位置等,方便在不同函数中访问和修改。
游戏的逻辑主要在game()函数中实现,通过不断循环和调用其他函数来实现蛇的移动、判断碰撞、更新游戏状态等功能。
代码中使用了 Windows API 来获取控制台句柄、设置光标位置和隐藏光标等,增强了游戏的控制台界面效果。

四、代码详解

一、头文件详解

cpp 复制代码
#include <iostream>
#include <string>
#include <fstream>
#include <windows.h>
#include <time.h>
#include <stdio.h>

iostream:提供输入输出流的功能,用于在控制台进行输入输出操作。
string:用于处理字符串操作。
fstream:用于文件输入输出操作,在这个游戏中用于读取和写入用户信息文件。
windows.h:提供了与 Windows 操作系统相关的功能,如获取控制台句柄、设置光标位置等。
time.h:用于获取时间,为随机数生成提供种子。
stdio.h:提供标准输入输出函数。

二、全局变量详解

cpp 复制代码
#define MAX   100
#define UP    1
#define DOWN  2
#define LEFT   3
#define RIGHT  4
#define MOVING 5
#define STOP   0

HANDLE hMain_Out = NULL;
HANDLE hMain_In = NULL;

struct Pos {
    int x;
    int y;
};

struct Body {
    int state;
    int len;
    int Direction;
    Pos pos[MAX];
};

Pos NewPos[MAX];
Pos Food;
SMALL_RECT Wall;
int count = 0;
int grade = 0;
int level = 1;
int amount = 0;
int speed = 200;
bool isPaused = false;

MAX:定义蛇身最大长度为 100。
UPDOWNLEFTRIGHT:分别代表蛇的上、下、左、右四个方向,用整数表示方便在代码中进行判断和操作。
MOVINGSTOP:表示蛇的移动和停止状态。
hMain_OuthMain_In:分别是控制台输出句柄和输入句柄,用于在控制台进行输出和获取输入。
struct Pos:定义了一个表示位置的结构体,包含两个整数成员xy,分别表示横坐标和纵坐标。
struct Body:定义了一个表示蛇的结构体,包含蛇的状态state、长度len、方向Direction和位置数组pos[MAX]
NewPos[MAX]:用于存储蛇身新位置的数组。
Food:表示食物位置的结构体。
Wall:表示墙壁范围的结构体。
count:用于记录蛇的移动步数。
grade:表示分数。
level:表示难度等级。
amount:表示食物数量。
speed:表示蛇的移动速度。
isPaused:表示游戏是否暂停的布尔变量。

三、函数详解

(一)HideTheCursor()函数

cpp 复制代码
void HideTheCursor() {
    CONSOLE_CURSOR_INFO cciCursor;

    HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

    if (GetConsoleCursorInfo(hStdOut, &cciCursor)) {
        cciCursor.bVisible = FALSE;
        SetConsoleCursorInfo(hStdOut, &cciCursor);
        SetConsoleCursorInfo(hStdOut, &cciCursor);
    }
}

首先定义了一个CONSOLE_CURSOR_INFO类型的变量cciCursor,用于存储控制台光标的信息。
通过GetStdHandle(STD_OUTPUT_HANDLE)获取标准输出句柄,赋值给hStdOut
使用GetConsoleCursorInfo(hStdOut, &cciCursor)获取当前控制台光标的信息,并存储在cciCursor中。
如果获取光标信息成功,将cciCursor.bVisible设置为FALSE,表示隐藏光标。
两次调用SetConsoleCursorInfo(hStdOut, &cciCursor)确保光标隐藏成功。

(二)basic()函数

cpp 复制代码
void basic() {
    HideTheCursor();

    system("cls");
    int choice;
    std::cout << "1. 登录" << std::endl;
    std::cout << "2. 注册" << std::endl;
    std::cout << "3. 游戏介绍" << std::endl;
    std::cout << "4. 退出" << std::endl;
    std::cout << "请选择: ";
    std::cin >> choice;

    while (true) {
        switch (choice) {
        case 1:
            if (login()) {
                // 登录成功后执行的代码
                for (int i = 5; i > 0; i--) {
                    system("cls");
                    std::cout << "登录成功,即将进入游戏!" << std::endl;
                    std::cout << "还有" << i << "秒即将开始游戏";
                    Sleep(1000);
                }
                game();
            }
            break;
        case 2:
            registerUser();
            break;
        case 3:
            gameIntroduction();
            break;
        case 4:
            std::cout << "请按ESC键退出游戏 ";
            while (true) {
                if (GetAsyncKeyState(VK_ESCAPE)) {
                    out();
                }
            }
            return;
        default:
            std::cout << "\n无效选择,请重新输入!" << std::endl;
            std::cin >> choice;
            break;
        }
        system("cls");
        std::cout << "1. 登录" << std::endl;
        std::cout << "2. 注册" << std::endl;
        std::cout << "3. 游戏介绍" << std::endl;
        std::cout << "4. 退出" << std::endl;
        std::cout << "请重新选择: ";
        std::cin >> choice;
    }

    return;
}

首先调用HideTheCursor()隐藏光标。
使用system("cls")清屏。
定义一个整数变量choice,用于存储用户的选择。
输出菜单选项,让用户选择登录、注册、游戏介绍或退出。
提示用户输入选择,并将输入存储在choice中。
进入一个无限循环,根据用户的选择执行相应的操作。
如果选择 1,调用login()函数进行登录,如果登录成功,显示登录成功信息并倒计时,然后调用game()函数进入游戏。
如果选择 2,调用registerUser()函数进行注册。
如果选择 3,调用gameIntroduction()函数显示游戏介绍。
如果选择 4,输出提示信息,然后进入一个循环,不断检测是否按下ESC键,如果按下则调用out()函数退出游戏。
如果选择无效,输出错误信息,让用户重新输入选择。
每次循环结束后,清屏并重新输出菜单选项,让用户重新选择。

(三)out()函数

cpp 复制代码
void out() {
    system("cls");
    std::cout << "\n\n\n\n\n\n\n\n\n\n\n\n\n\n                                           感谢您的使用,再见!" << std::endl;
    Sleep(1000);
    for (int i = 3; i > 0; i--) {
        system("cls");
        std::cout << "正在退出";
        Sleep(400);
        std::cout << ".";
        Sleep(400);
        std::cout << ".";
        Sleep(400);
        std::cout << ".";
    }
    system("cls");
    exit(0);
}

使用system("cls")清屏。
输出感谢信息和再见提示。
使用Sleep(1000)暂停 1 秒,给用户时间阅读感谢信息。
使用循环模拟逐步退出的效果,每次循环输出 "正在退出" 和三个点,每个点之间暂停 400 毫秒。
最后再次清屏,调用exit(0)退出程序。

(四)login()函数

cpp 复制代码
bool login() {
    system("cls");
    std::string username, password;
    std::cout << "请输入用户名: ";
    std::cin >> username;

    // 检查用户名是否为空
    if (username.empty()) {
        std::cout << "请重新输入!" << std::endl;
        MessageBox(NULL, "用户名不能为空", "提示", 0);
        return false;
    }

    std::cout << "请输入密码: ";
    std::cin >> password;

    // 检查密码是否为空
    if (password.empty()) {
        std::cout << "请重新输入!" << std::endl;
        MessageBox(NULL, "密码不能为空", "提示", 0);
        return false;
    }

    std::ifstream inFile("user_info.txt");
    std::string storedUsername, storedPassword;
    if (inFile >> storedUsername >> storedPassword) {
        if (username == storedUsername && password == storedPassword) {
            inFile.close();
            return true;
        }
    }
    inFile.close();
    std::cout << "请重新输入!" << std::endl;
    MessageBox(NULL, "登录失败", "提示", 0);
    return false;
}

使用system("cls")清屏。
定义两个字符串变量usernamepassword,分别用于存储用户输入的用户名和密码。
提示用户输入用户名,并将输入存储在username中。
检查用户名是否为空,如果为空则输出错误信息并显示提示框,然后返回false
提示用户输入密码,并将输入存储在password中。
检查密码是否为空,如果为空则输出错误信息并显示提示框,然后返回false
打开用户信息文件user_info.txt,读取存储的用户名和密码。
如果读取成功,比较输入的用户名和密码与文件中的是否一致,如果一致则关闭文件并返回true,表示登录成功;如果不一致则关闭文件,输出错误信息并显示提示框,然后返回false

(五)registerUser()函数

cpp 复制代码
void registerUser() {
    std::string newUsername, newPassword;
    std::cout << "请输入新用户名: ";
    std::cin >> newUsername;

    // 检查用户名是否为空
    if (newUsername.empty()) {
        std::cout << "用户名不能为空,请重新输入!" << std::endl;
        MessageBox(NULL, "提示", "提示", 0);
        return;
    }

    std::cout << "请输入新密码: ";
    std::cin >> newPassword;

    // 检查密码是否为空
    if (newPassword.empty()) {
        std::cout << "请重新输入!" << std::endl;
        MessageBox(NULL, "密码不能为空", "提示", 0);
        return;
    }

    std::ofstream outFile("user_info.txt");
    if (outFile.is_open()) {
        outFile << newUsername << " " << newPassword;
        outFile.close();
        MessageBox(NULL, "注册成功!", "提示", 0);
    }
    else {
        std::cout << "注册失败,无法保存用户信息!" << std::endl;
        MessageBox(NULL, "注册失败,无法保存用户信息!", "提示", 0);
    }
}

首先定义两个std::string类型的变量newUsernamenewPassword,分别用于存储用户输入的新用户名和新密码。
输出提示信息让用户输入新用户名,并使用std::cin读取用户输入,将其存储在newUsername中。
检查newUsername是否为空字符串。如果是,则输出错误信息 "用户名不能为空,请重新输入!",并通过MessageBox(NULL, "提示", "提示", 0)弹出一个提示框。然后函数直接返回,不进行后续操作。
输出提示信息让用户输入新密码,并使用std::cin读取用户输入,将其存储在newPassword中。
检查newPassword是否为空字符串。如果是,则输出错误信息 "请重新输入!",并通过MessageBox(NULL, "密码不能为空", "提示", 0)弹出一个提示框。然后函数直接返回,不进行后续操作。
创建一个std::ofstream类型的对象outFile,并尝试打开文件 "user_info.txt" 用于写入。
如果文件成功打开,将用户输入的新用户名和新密码写入文件,格式为 "用户名 密码"。然后关闭文件,并通过MessageBox(NULL, "注册成功!", "提示", 0)弹出一个注册成功的提示框。
如果文件打开失败,则输出错误信息 "注册失败,无法保存用户信息!",并通过MessageBox(NULL, "注册失败,无法保存用户信息!", "提示", 0)弹出一个提示框。

(六)gameIntroduction()函数

cpp 复制代码
void gameIntroduction() {
    for (int i = 5; i > 0; i--) {
        system("cls");
        std::cout << "欢迎来到贪吃蛇游戏!" << std::endl;
        std::cout << "游戏规则:控制蛇的移动来吃掉食物,蛇身会变长,碰到墙壁或自身则游戏结束。" << std::endl;
        std::cout << "通过不断吃食物得分并提升难度等级。\n\n" << std::endl;
        std::cout << "                     还有" << i << "秒后自动返回大厅";
        Sleep(1000);
    }
}

使用一个for循环,从 5 递减到 1,模拟倒计时效果。
在每次循环中,首先调用system("cls")清屏。
然后输出欢迎信息和游戏规则说明。
接着输出倒计时信息,显示还有多少秒后自动返回大厅。
最后调用Sleep(1000)暂停 1 秒,模拟时间流逝。

(七)Init(Body& b)函数

cpp 复制代码
void Init(Body& b) {
    b.len = 3;
    b.Direction = RIGHT;
    b.state = STOP;
    b.pos[0].x = 2;
    b.pos[0].y = 1;
    b.pos[1].x = 4;
    b.pos[1].y = 1;
    b.pos[2].x = 6;
    b.pos[2].y = 1;
    hMain_Out = GetStdHandle(STD_OUTPUT_HANDLE);
    hMain_In = GetStdHandle(STD_INPUT_HANDLE);
    CreateWall();
    CreateFood();
    ShowInfo();
}

设置传入的蛇结构体b的初始长度为 3,初始方向为向右(RIGHT),初始状态为停止(STOP)
分别设置蛇身三个初始位置的横坐标和纵坐标。
通过GetStdHandle(STD_OUTPUT_HANDLE)GetStdHandle(STD_INPUT_HANDLE)获取控制台的输出句柄和输入句柄,并分别赋值给全局变量hMain_OuthMain_In
调用CreateWall()函数创建游戏的墙壁。
调用CreateFood()函数随机生成食物的位置。
调用ShowInfo()函数显示游戏的信息,如分数和难度等级。

(八)Print(const Body& b)函数

cpp 复制代码
void Print(const Body& b) {
    COORD coord;
    for (int ix = b.len - 1; ix >= 0; --ix) {
        coord.X = b.pos[ix].x;
        coord.Y = b.pos[ix].y;
        SetConsoleCursorPosition(hMain_Out, coord);
        printf("●");
    }
}

定义一个COORD类型的变量coord,用于存储光标位置。
使用一个for循环,从蛇身的最后一个位置开始,逆序遍历蛇身的所有位置。
在每次循环中,将当前位置的横坐标和纵坐标赋值给coord
通过SetConsoleCursorPosition(hMain_Out, coord)将控制台光标移动到当前蛇身位置。
使用printf("●")在当前光标位置输出蛇身的圆形符号 "●"。

(九)Print(int x, int y)函数

cpp 复制代码
void Print(int x, int y) {
    COORD c;
    c.X = x;
    c.Y = y;
    SetConsoleCursorPosition(hMain_Out, c);
    printf("■");
}

定义一个COORD类型的变量c,用于存储光标位置。
将传入的横坐标x和纵坐标y赋值给c。
通过SetConsoleCursorPosition(hMain_Out, c)将控制台光标移动到指定位置。
使用printf("■")在当前光标位置输出用于创建墙壁或食物的符号 "■"。

(十)Move(Body& b)函数

cpp 复制代码
void Move(Body& b) {
	ShowInfo();
	if (IsKnock_Wall(b)) {
		MessageBox(NULL, "You are dead!", "Oh my God", 0);
		game();
	}
	if (IsKnock_Food(b)) {
		if (amount > 5) {
			++level;
			amount = 0;
			speed -= 50;
		}
		AddBody(b);
		grade += 10;
		++amount;
		Clean(Food.x, Food.y);
		CreateFood();
	}
	if (STOP == b.state) {
		if (RIGHT == b.Direction) {
			for (int ix = 0; ix < b.len; ++ix) {
				Clean(b.pos[ix].x, b.pos[ix].y);
				b.pos[ix].x += 2;
			}
		}
		if (UP == b.Direction) {
			for (int ix = 0; ix < b.len; ++ix) {
				Clean(b.pos[ix].x, b.pos[ix].y);
				b.pos[ix].y--;
			}
		}
		if (DOWN == b.Direction) {
			for (int ix = 0; ix < b.len; ++ix) {
				Clean(b.pos[ix].x, b.pos[ix].y);
				b.pos[ix].y++;
			}
		}
		if (LEFT == b.Direction) {
			for (int ix = 0; ix < b.len; ++ix) {
				Clean(b.pos[ix].x, b.pos[ix].y);
				b.pos[ix].x -= 2;
			}
		}
	}

	if (MOVING == b.state) {
		PosCopy(b, NewPos);
		if (UP == b.Direction) {
			if (b.len == count) {
				b.state = STOP;
				b.Direction = UP;
				count = 0;
			}
			if (count < b.len && MOVING == b.state) {
				b.pos[b.len - 1].y--;
				Clean(b.pos[0].x, b.pos[0].y);
				MoveBody(b);
				Print(b);
			}
		}
		if (DOWN == b.Direction) {
			if (b.len == count) {
				b.state = STOP;
				b.Direction = DOWN;
				count = 0;
			}
			if (count < b.len && MOVING == b.state) {
				b.pos[b.len - 1].y++;
				Clean(b.pos[0].x, b.pos[0].y);
				MoveBody(b);
				Print(b);
			}
		}
		if (LEFT == b.Direction) {
			if (b.len == count) {
				b.state = STOP;
				b.Direction = LEFT;
				count = 0;
			}
			if (count < b.len && MOVING == b.state) {
				b.pos[b.len - 1].x -= 2;
				Clean(b.pos[0].x, b.pos[0].y);
				MoveBody(b);
				Print(b);
			}
		}
		if (RIGHT == b.Direction) {
			if (b.len == count) {
				b.state = STOP;
				b.Direction = RIGHT;
				count = 0;
			}
			if (count < b.len && MOVING == b.state) {
				b.pos[b.len - 1].x += 2;
				Clean(b.pos[0].x, b.pos[0].y);
				MoveBody(b);
				Print(b);
			}
		}
	}
	Print(b);
}

首先调用ShowInfo()函数显示游戏信息。
调用IsKnock_Wall(b)函数判断蛇是否碰到墙壁。如果碰到墙壁,则弹出一个提示框 "You are dead!",并调用game()函数重新开始游戏。
调用IsKnock_Food(b)函数判断蛇是否吃到食物。如果吃到食物,则进行一系列操作,包括增加难度等级、增加蛇身长度、更新分数、增加食物数量、清除食物位置并重新生成食物。
如果蛇的状态为停止(STOP),根据蛇的当前方向进行相应的移动操作。如果方向为向右,则将蛇身每个位置的横坐标增加 2;如果方向为向上,则将蛇身每个位置的纵坐标减 1;如果方向为向下,则将蛇身每个位置的纵坐标加 1;如果方向为向左,则将蛇身每个位置的横坐标减 2。在移动前,先调用Clean(b.pos[ix].x, b.pos[ix].y)清除当前位置的显示。

(十一)GetDirection(Body& b)函数

cpp 复制代码
int GetDirection(Body& b) {
    if (GetAsyncKeyState(VK_UP)) {
        count = 0;
        TurnRound(UP, b);
    }
    if (GetAsyncKeyState(VK_DOWN)) {
        count = 0;
        TurnRound(DOWN, b);
    }
    if (GetAsyncKeyState(VK_LEFT)) {
        count = 0;
        TurnRound(LEFT, b);
    }
    if (GetAsyncKeyState(VK_RIGHT)) {
        count = 0;
        TurnRound(RIGHT, b);
    }
    return 0;
}

这个函数用于获取用户输入的方向键,并根据输入调用TurnRound函数来改变蛇的移动方向。
分别使用GetAsyncKeyState函数检测用户是否按下了上(VK_UP)、下(VK_DOWN)、左(VK_LEFT)、右(VK_RIGHT)方向键。
如果检测到用户按下了某个方向键,首先将全局变量count重置为 0。这可能是用于控制蛇移动的步数或其他相关操作的计数变量。
然后调用TurnRound函数,并传入相应的方向常量(如UPDOWNLEFTRIGHT)以及蛇的结构体引用b,以实现改变蛇的移动方向。
最后函数返回 0,可能表示没有特定的返回值需求,只是一个占位的返回值。

(十二)TurnRound(int d, Body& b)函数

cpp 复制代码
void TurnRound(int d, Body& b) {
    switch (d) {
    case UP:
        if (RIGHT == b.Direction || LEFT == b.Direction) {
            PosCopy(b, NewPos);
            --b.pos[b.len - 1].y;
            Clean(b.pos[0].x, b.pos[0].y);
            MoveBody(b);
            Print(b);
            b.Direction = d;
            b.state = MOVING;
        }
        break;
    case DOWN:
        if (RIGHT == b.Direction || LEFT == b.Direction) {
            PosCopy(b, NewPos);
            ++b.pos[b.len - 1].y;
            Clean(b.pos[0].x, b.pos[0].y);
            MoveBody(b);
            Print(b);
            b.Direction = d;
            b.state = MOVING;
        }
        break;
    case LEFT:
        if (UP == b.Direction || DOWN == b.Direction) {
            PosCopy(b, NewPos);
            b.pos[b.len - 1].x -= 2;
            Clean(b.pos[0].x, b.pos[0].y);
            MoveBody(b);
            Print(b);
            b.Direction = d;
            b.state = MOVING;
        }
        break;
    case RIGHT:
        if (UP == b.Direction || DOWN == b.Direction) {
            PosCopy(b, NewPos);
            b.pos[b.len - 1].x += 2;
            Clean(b.pos[0].x, b.pos[0].y);
            MoveBody(b);
            Print(b);
            b.Direction = d;
            b.state = MOVING;
        }
        break;
    default:
        break;
    }
}

这个函数根据传入的方向参数d来改变蛇的移动方向。
使用switch语句根据传入的方向常量进行不同的操作。
例如,当dUP(向上)时,如果蛇当前的方向是向右(RIGHT)或向左(LEFT),则进行一系列操作:
调用PosCopy(b, NewPos)函数,可能是将蛇的当前位置复制到一个临时数组中,以便后续操作。
将蛇头的纵坐标减 1,表示向上移动一格。
调用Clean(b.pos[0].x, b.pos[0].y)函数,可能是清除蛇尾的显示。
调用MoveBody(b)函数,可能是更新蛇身的位置。
调用Print(b)函数,输出蛇的新位置。
设置蛇的新方向为向上(UP),并将蛇的状态设置为移动状态(MOVING)
对于其他方向的处理类似,根据不同的方向改变蛇头的位置,并进行相应的操作来更新蛇的状态和显示。

(十三)PosCopy(Body& b, Pos NewPos[])函数

cpp 复制代码
void PosCopy(Body& b, Pos NewPos[]) {
    for (int ix = 0; ix < b.len; ++ix) {
        NewPos[ix].x = 0;
        NewPos[ix].y = 0;
    }
    for (int ix = 0; ix < b.len; ++ix) {
        NewPos[ix] = b.pos[ix];
    }
}

这个函数用于将蛇的当前位置复制到一个临时数组中。
首先使用一个for循环将临时数组NewPos中的每个位置的横坐标和纵坐标初始化为 0
然后使用另一个for循环将蛇的当前位置(存储在结构体bpos数组中)复制到临时数组NewPos中。这样可以在后续的操作中保留蛇的当前位置,以便进行移动和方向改变等操作时使用。

(十四)MoveBody(Body& b)函数

cpp 复制代码
void MoveBody(Body& b) {
    for (int ix = b.len - 1; ix > 0; --ix) {
        b.pos[ix - 1] = NewPos[ix];
    }
    ++count;
    PosCopy(b, NewPos);
}

这个函数用于更新蛇身的位置。
使用一个for循环,从蛇尾开始,将每个位置更新为前一个位置的值(从临时数组NewPos中获取)。这样实现了蛇身的移动效果。
增加全局变量count的值,可能用于记录蛇的移动步数或其他相关操作的计数。
再次调用PosCopy(b, NewPos)函数,将蛇的当前位置复制到临时数组NewPos中,为下一次移动做好准备。

(十五)HideCursor()函数

cpp 复制代码
void HideCursor() {
    CONSOLE_CURSOR_INFO info;
    GetConsoleCursorInfo(hMain_Out, &info);
    info.bVisible = FALSE;
    SetConsoleCursorInfo(hMain_Out, &info);
}

这个函数用于隐藏控制台光标。
定义一个CONSOLE_CURSOR_INFO类型的变量info,用于存储控制台光标的信息。
使用GetConsoleCursorInfo(hMain_Out, &info)函数获取当前控制台光标的信息,并存储在info中。
info.bVisible设置为FALSE,表示隐藏光标。
使用SetConsoleCursorInfo(hMain_Out, &info)函数设置控制台光标的信息,实现隐藏光标的效果。

(十六)CreateWall()函数

cpp 复制代码
void CreateWall() {
    CONSOLE_SCREEN_BUFFER_INFO info;
    GetConsoleScreenBufferInfo(hMain_Out, &info);
    info.srWindow.Right -= 19;
    info.srWindow.Bottom -= 5;
    Wall = info.srWindow;
    for (int i = 0; i <= info.srWindow.Right; i += 2) {
        Print(i, info.srWindow.Top);
        Print(i, info.srWindow.Bottom);
    }
    for (int y = 0; y <= info.srWindow.Bottom; ++y) {
        Print(0, y);
        Print(info.srWindow.Right, y);
    }
}

这个函数用于创建游戏的墙壁。
定义一个CONSOLE_SCREEN_BUFFER_INFO类型的变量info,用于存储控制台屏幕缓冲区的信息。
使用GetConsoleScreenBufferInfo(hMain_Out, &info)函数获取当前控制台屏幕缓冲区的信息,并存储在info中。
调整info.srWindow.Rightinfo.srWindow.Bottom的值,减小墙壁的范围。这可能是为了在控制台中留出一些空间用于显示其他信息或使游戏界面更加美观。
将调整后的窗口信息赋值给全局变量Wall,可能用于后续判断蛇是否碰到墙壁等操作。
使用两个嵌套的循环在控制台边界绘制墙壁。外层循环遍历水平方向的坐标,内层循环遍历上下边界的纵坐标,调用Print(i, info.srWindow.Top)Print(i, info.srWindow.Bottom)在上下边界绘制墙壁;另一个外层循环遍历垂直方向的坐标,内层循环遍历左右边界的横坐标,调用Print(0, y)Print(info.srWindow.Right, y)在左右边界绘制墙壁。

(十七)CreateFood()函数、

cpp 复制代码
void CreateFood() {
    srand(unsigned(time(NULL)));
    unsigned x_t = RAND_MAX / Wall.Right;
    unsigned y_t = RAND_MAX / Wall.Bottom;
    while (true) {
        int x = rand() / x_t;
        int y = rand() / y_t;
        Food.x = x - 4;
        Food.y = y - 4;
        if ((0 == Food.x % 2) && (0 == Food.y % 2)) {
            if (Food.x < 5) {
                Food.x += 8;
            }
            if (Food.y < 5) {
                Food.y += 8;
            }
            Print(Food.x, Food.y);
            break;
        }
    }
}

这个函数用于随机生成食物的位置。
首先使用srand(unsigned(time(NULL)))设置随机数生成器的种子,以确保每次生成的随机数序列不同。
计算x_ty_t,分别是随机数范围与墙壁右边界和下边界的比例。
进入一个无限循环,在每次循环中生成随机坐标xy,并根据x_ty_t进行调整。然后将生成的坐标赋值给食物位置结构体Food的横坐标和纵坐标,并进行一些调整:
如果食物的横坐标小于 5,则将其加上 8,确保食物不在靠近边界的位置。
如果食物的纵坐标小于 5,则将其加上 8,同样确保食物不在靠近边界的位置。
检查食物的坐标是否为偶数(即(0 == Food.x % 2) && (0 == Food.y % 2)),如果是偶数,则在该位置输出食物(调用Print(Food.x, Food.y)),并跳出循环。

(十八)IsKnock_Food(const Body& b)函数

cpp 复制代码
bool IsKnock_Food(const Body& b) {
    if (b.pos[b.len - 1].x == Food.x && b.pos[b.len - 1].y == Food.y) {
        return true;
    }
    else {
        return false;
    }
}

这个函数用于判断蛇是否碰到食物。
检查蛇的头部位置(即结构体b的最后一个位置b.pos[b.len - 1])的横坐标和纵坐标是否与食物位置Food的横坐标和纵坐标相等。
如果相等,则返回true,表示蛇碰到了食物;否则返回false

(十九)IsKnock_Wall(const Body& b)函数

cpp 复制代码
bool IsKnock_Wall(const Body& b) {
    if (0 == b.pos[b.len - 1].x || 0 == b.pos[b.len - 1].y || Wall.Right == b.pos[b.len - 1].x || Wall.Bottom == b.pos[b.len - 1].y) {
        return true;
    }
    Pos Head = b.pos[b.len - 1];
    for (int ix = 0; ix <= b.len - 3; ++ix) {
        if (Head.x == b.pos[ix].x && Head.y == b.pos[ix].y) {
            return true;
        }
    }
    return false;
}

这个函数用于判断蛇是否碰到墙壁或自身。
首先检查蛇的头部位置是否在墙壁的边界上,即检查蛇头的横坐标是否为 0、纵坐标是否为 0、横坐标是否等于墙壁右边界Wall.Right或纵坐标是否等于墙壁下边界Wall.Bottom。如果是,则返回true,表示蛇碰到了墙壁。
如果蛇头不在墙壁边界上,则定义一个Pos类型的变量Head,将其赋值为蛇头的位置。然后使用一个循环遍历蛇身的其他部分(从第一个位置到倒数第三个位置),检查蛇头的位置是否与蛇身的其他部分重叠(即横坐标和纵坐标都相等)。如果重叠,则返回true,表示蛇碰到了自身。
如果蛇既没有碰到墙壁也没有碰到自身,则返回false

(二十)ShowInfo()函数

cpp 复制代码
void ShowInfo() {
    COORD c;
    c.X = Wall.Right + 2;
    c.Y = 3;
    SetConsoleCursorPosition(hMain_Out, c);
    printf("  分数:%d", grade);
    c.Y += 10;
    SetConsoleCursorPosition(hMain_Out, c);
    printf("  难度等级:%d", level);
}

这个函数用于在控制台显示游戏信息,包括分数和难度等级。
定义一个COORD类型的变量c,用于存储光标位置。
设置c.X为墙壁右边界Wall.Right + 2,表示在墙壁右侧留出一些空间,设置c.Y3,表示在第三行显示信息。
使用SetConsoleCursorPosition(hMain_Out, c)将控制台光标移动到指定位置,然后输出分数信息,格式为 "分数:[具体分数]",其中具体分数存储在全局变量grade中。
增加c.Y的值为 13,表示在第十三行显示下一个信息。
再次使用SetConsoleCursorPosition(hMain_Out, c)将控制台光标移动到指定位置,然后输出难度等级信息,格式为 "难度等级:[具体等级]",其中具体等级存储在全局变量level中。

(二十一)AddBody(Body& b)函数

cpp 复制代码
void AddBody(Body& b) {
    if (b.len < MAX) {
        if (UP == b.Direction) {
            b.pos[b.len].y = b.pos[b.len - 1].y - 1;
            b.pos[b.len].x = b.pos[b.len - 1].x;
            ++b.len;
        }
        if (DOWN == b.Direction) {
            b.pos[b.len].y = b.pos[b.len - 1].y + 1;
            b.pos[b.len].x = b.pos[b.len - 1].x;
            ++b.len;
        }
        if (LEFT == b.Direction) {
            b.pos[b.len].x = b.pos[b.len - 1].x - 2;
            b.pos[b.len].y = b.pos[b.len - 1].y;
            ++b.len;
        }
        if (RIGHT == b.Direction) {
            b.pos[b.len].x = b.pos[b.len - 1].x + 2;
            b.pos[b.len].y = b.pos[b.len - 1].y;
            ++b.len;
        }
    }
}

这个函数用于增加蛇的长度。
首先检查蛇的长度是否小于最大长度MAX。如果是,则根据蛇的当前方向在蛇尾添加一个新的位置:
如果方向为向上(UP),则将新位置的纵坐标设置为蛇尾位置的纵坐标减 1,横坐标与蛇尾位置相同,然后增加蛇的长度。
如果方向为向下(DOWN),则将新位置的纵坐标设置为蛇尾位置的纵坐标加 1,横坐标与蛇尾位置相同,然后增加蛇的长度。
如果方向为向左(LEFT),则将新位置的横坐标设置为蛇尾位置的横坐标减 2,纵坐标与蛇尾位置相同,然后增加蛇的长度。
如果方向为向右(RIGHT),则将新位置的横坐标设置为蛇尾位置的横坐标加 2,纵坐标与蛇尾位置相同,然后增加蛇的长度。

四、主函数详解

cpp 复制代码
int main() {
    system("mode con cols=100 lines=30");
    system("title 贪吃蛇");

    basic();

    return 0;
}

在主函数中,首先使用system("mode con cols=100 lines=30")设置控制台的宽度为 100 个字符,高度为 30 行。
然后使用system("title 贪吃蛇")设置控制台窗口的标题为 "贪吃蛇"。
最后调用basic()函数,进入游戏的基础功能模块,开始游戏的流程。

五、完整代码

cpp 复制代码
#include <iostream>
#include <string>
#include <fstream>
#include <windows.h>
#include <time.h>
#include <stdio.h>
#include <limits>

#define MAX   100
#define UP    1
#define DOWN  2
#define LEFT   3
#define RIGHT  4
#define MOVING 5
#define STOP   0

HANDLE hMain_Out = NULL;
HANDLE hMain_In = NULL;

struct Pos {
	int x;
	int y;
};

struct Body {
	int state;
	int len;
	int Direction;
	Pos pos[MAX];
};

Pos NewPos[MAX];
Pos Food;
SMALL_RECT Wall;
int count = 0;
int grade = 0;
int level = 1;
int amount = 0;
int speed = 200;
bool isPaused = false;

void basic();
void game();
void out();
bool login();
void registerUser();
void gameIntroduction();
void Init(Body& b);
void Print(const Body& b);
void Print(int x, int y);
void Move(Body& b);
void Clean(int x, int y);
void Clean(const Body& b);
void ShowInfo();
int GetDirection(Body& b);
void TurnRound(int Direction, Body& b);
void PosCopy(Body& b, Pos NewPos[]);
void MoveBody(Body& b);
void HideCursor();
void CreateWall();
void CreateFood();
bool IsKnock_Food(const Body& b);
bool IsKnock_Wall(const Body& b);
void AddBody(Body& b);
void HideTheCursor();

void basic() {

	HideTheCursor();

	system("cls");
	int choice;
	std::cout << "1. 登录" << std::endl;
	std::cout << "2. 注册" << std::endl;
	std::cout << "3. 游戏介绍" << std::endl;
	std::cout << "4. 退出" << std::endl;
	std::cout << "请选择: ";
	std::cin >> choice;

	while (true) {
		switch (choice) {
		case 1:
			if (login()) {
				// 登录成功后执行的代码
				for (int i = 5; i > 0; i--) {
					system("cls");
					std::cout << "登录成功,即将进入游戏!" << std::endl;
					std::cout << "还有" << i << "秒即将开始游戏";
					Sleep(1000);
				}
				game();
			}
			break;
		case 2:
			registerUser();
			break;
		case 3:
			gameIntroduction();
			break;
		case 4:
			std::cout << "请按ESC键退出游戏 ";
			while (true) {
				if (GetAsyncKeyState(VK_ESCAPE)) {
					out();
				}
			}
			return;
		default:
			std::cout << "\n无效选择,请重新输入!" << std::endl;
			std::cin >> choice;
			break;
		}
		system("cls");
		std::cout << "1. 登录" << std::endl;
		std::cout << "2. 注册" << std::endl;
		std::cout << "3. 游戏介绍" << std::endl;
		std::cout << "4. 退出" << std::endl;
		std::cout << "请重新选择: ";
		std::cin >> choice;
	}

	return;
}

void game() {
	system("cls");
	Body b;
	Init(b);
	Print(b);
	HideCursor();
	while (TRUE) {
		if (GetAsyncKeyState(VK_ESCAPE)) { 
			out();
		}
		if (GetAsyncKeyState(VK_SPACE)) { 
			isPaused = !isPaused;
			while (isPaused && GetAsyncKeyState(VK_SPACE)) {
				Sleep(100);
			}
		}
		if (!isPaused) { 
			Sleep(speed);
			Move(b);
			GetDirection(b);
		}
	}
}

void out() {
	system("cls");
	std::cout << "\n\n\n\n\n\n\n\n\n\n\n\n\n\n                                           感谢您的使用,再见!" << std::endl;
	Sleep(1000);
	for (int i = 3; i > 0; i--) {
		system("cls");
		std::cout << "正在退出";
		Sleep(400);
		std::cout << ".";
		Sleep(400);
		std::cout << ".";
		Sleep(400);
		std::cout << ".";
	}
	system("cls");
	exit(0);
}

// 登录函数
bool login() {
	system("cls");
	std::string username, password;
	std::cout << "请输入用户名: ";
	std::cin >> username;

	// 检查用户名是否为空
	if (username.empty()) {
		std::cout << "请重新输入!" << std::endl;
		MessageBox(NULL, "用户名不能为空", "提示", 0);
		return false;
	}

	std::cout << "请输入密码: ";
	std::cin >> password;

	// 检查密码是否为空
	if (password.empty()) {
		std::cout << "请重新输入!" << std::endl;
		MessageBox(NULL, "密码不能为空", "提示", 0);
		return false;
	}

	std::ifstream inFile("user_info.txt");
	std::string storedUsername, storedPassword;
	if (inFile >> storedUsername >> storedPassword) {
		if (username == storedUsername && password == storedPassword) {
			inFile.close();
			return true;
		}
	}
	inFile.close();
	std::cout << "请重新输入!" << std::endl;
	MessageBox(NULL, "登录失败", "提示", 0);
	return false;
}

// 注册函数
void registerUser() {
	std::string newUsername, newPassword;
	std::cout << "请输入新用户名: ";
	std::cin >> newUsername;

	// 检查用户名是否为空
	if (newUsername.empty()) {
		std::cout << "用户名不能为空,请重新输入!" << std::endl;
		MessageBox(NULL, "提示", "提示", 0);
		return;
	}

	std::cout << "请输入新密码: ";
	std::cin >> newPassword;

	// 检查密码是否为空
	if (newPassword.empty()) {
		std::cout << "请重新输入!" << std::endl;
		MessageBox(NULL, "密码不能为空", "提示", 0);
		return;
	}

	std::ofstream outFile("user_info.txt");
	if (outFile.is_open()) {
		outFile << newUsername << " " << newPassword;
		outFile.close();
		MessageBox(NULL, "注册成功!", "提示", 0);
	}
	else {
		std::cout << "注册失败,无法保存用户信息!" << std::endl;
		MessageBox(NULL, "注册失败,无法保存用户信息!", "提示", 0);
	}
}

// 游戏介绍界面
void gameIntroduction() {
	for (int i = 5; i > 0; i--) {
		system("cls");
		std::cout << "欢迎来到贪吃蛇游戏!" << std::endl;
		std::cout << "游戏规则:控制蛇的移动来吃掉食物,蛇身会变长,碰到墙壁或自身则游戏结束。" << std::endl;
		std::cout << "通过不断吃食物得分并提升难度等级。\n\n" << std::endl;
		std::cout << "                     还有" << i << "秒后自动返回大厅";
		Sleep(1000);
	}
}

void Init(Body& b) {
	b.len = 3;
	b.Direction = RIGHT;
	b.state = STOP;
	b.pos[0].x = 2;
	b.pos[0].y = 1;
	b.pos[1].x = 4;
	b.pos[1].y = 1;
	b.pos[2].x = 6;
	b.pos[2].y = 1;
	hMain_Out = GetStdHandle(STD_OUTPUT_HANDLE);
	hMain_In = GetStdHandle(STD_INPUT_HANDLE);
	CreateWall();
	CreateFood();
	ShowInfo();
}

void Print(const Body& b) {
	COORD coord;
	for (int ix = b.len - 1; ix >= 0; --ix) {
		coord.X = b.pos[ix].x;
		coord.Y = b.pos[ix].y;
		SetConsoleCursorPosition(hMain_Out, coord);
		printf("●");
	}
}

void Move(Body& b) {
	ShowInfo();
	if (IsKnock_Wall(b)) {
		MessageBox(NULL, "You are dead!", "Oh my God", 0);
		game();
	}
	if (IsKnock_Food(b)) {
		if (amount > 5) {
			++level;
			amount = 0;
			speed -= 50;
		}
		AddBody(b);
		grade += 10;
		++amount;
		Clean(Food.x, Food.y);
		CreateFood();
	}
	if (STOP == b.state) {
		if (RIGHT == b.Direction) {
			for (int ix = 0; ix < b.len; ++ix) {
				Clean(b.pos[ix].x, b.pos[ix].y);
				b.pos[ix].x += 2;
			}
		}
		if (UP == b.Direction) {
			for (int ix = 0; ix < b.len; ++ix) {
				Clean(b.pos[ix].x, b.pos[ix].y);
				b.pos[ix].y--;
			}
		}
		if (DOWN == b.Direction) {
			for (int ix = 0; ix < b.len; ++ix) {
				Clean(b.pos[ix].x, b.pos[ix].y);
				b.pos[ix].y++;
			}
		}
		if (LEFT == b.Direction) {
			for (int ix = 0; ix < b.len; ++ix) {
				Clean(b.pos[ix].x, b.pos[ix].y);
				b.pos[ix].x -= 2;
			}
		}
	}

	if (MOVING == b.state) {
		PosCopy(b, NewPos);
		if (UP == b.Direction) {
			if (b.len == count) {
				b.state = STOP;
				b.Direction = UP;
				count = 0;
			}
			if (count < b.len && MOVING == b.state) {
				b.pos[b.len - 1].y--;
				Clean(b.pos[0].x, b.pos[0].y);
				MoveBody(b);
				Print(b);
			}
		}
		if (DOWN == b.Direction) {
			if (b.len == count) {
				b.state = STOP;
				b.Direction = DOWN;
				count = 0;
			}
			if (count < b.len && MOVING == b.state) {
				b.pos[b.len - 1].y++;
				Clean(b.pos[0].x, b.pos[0].y);
				MoveBody(b);
				Print(b);
			}
		}
		if (LEFT == b.Direction) {
			if (b.len == count) {
				b.state = STOP;
				b.Direction = LEFT;
				count = 0;
			}
			if (count < b.len && MOVING == b.state) {
				b.pos[b.len - 1].x -= 2;
				Clean(b.pos[0].x, b.pos[0].y);
				MoveBody(b);
				Print(b);
			}
		}
		if (RIGHT == b.Direction) {
			if (b.len == count) {
				b.state = STOP;
				b.Direction = RIGHT;
				count = 0;
			}
			if (count < b.len && MOVING == b.state) {
				b.pos[b.len - 1].x += 2;
				Clean(b.pos[0].x, b.pos[0].y);
				MoveBody(b);
				Print(b);
			}
		}
	}
	Print(b);
}

void Clean(int x, int y) {
	COORD c;
	c.X = x;
	c.Y = y;
	SetConsoleCursorPosition(hMain_Out, c);
	printf(" ");
}
int GetDirection(Body& b) {
	if (GetAsyncKeyState(VK_UP)) {
		count = 0;
		TurnRound(UP, b);
	}
	if (GetAsyncKeyState(VK_DOWN)) {
		count = 0;
		TurnRound(DOWN, b);
	}
	if (GetAsyncKeyState(VK_LEFT)) {
		count = 0;
		TurnRound(LEFT, b);
	}
	if (GetAsyncKeyState(VK_RIGHT)) {
		count = 0;
		TurnRound(RIGHT, b);
	}
	return 0;
}

void TurnRound(int d, Body& b) {
	switch (d) {
	case UP:
		if (RIGHT == b.Direction || LEFT == b.Direction) {
			PosCopy(b, NewPos);
			--b.pos[b.len - 1].y;
			Clean(b.pos[0].x, b.pos[0].y);
			MoveBody(b);
			Print(b);
			b.Direction = d;
			b.state = MOVING;
		}
		break;
	case DOWN:
		if (RIGHT == b.Direction || LEFT == b.Direction) {
			PosCopy(b, NewPos);
			++b.pos[b.len - 1].y;
			Clean(b.pos[0].x, b.pos[0].y);
			MoveBody(b);
			Print(b);
			b.Direction = d;
			b.state = MOVING;
		}
		break;
	case LEFT:
		if (UP == b.Direction || DOWN == b.Direction) {
			PosCopy(b, NewPos);
			b.pos[b.len - 1].x -= 2;
			Clean(b.pos[0].x, b.pos[0].y);
			MoveBody(b);
			Print(b);
			b.Direction = d;
			b.state = MOVING;
		}
		break;
	case RIGHT:
		if (UP == b.Direction || DOWN == b.Direction) {
			PosCopy(b, NewPos);
			b.pos[b.len - 1].x += 2;
			Clean(b.pos[0].x, b.pos[0].y);
			MoveBody(b);
			Print(b);
			b.Direction = d;
			b.state = MOVING;
		}
		break;
	default:
		break;
	}
}

void PosCopy(Body& b, Pos NewPos[]) {
	for (int ix = 0; ix < b.len; ++ix) {
		NewPos[ix].x = 0;
		NewPos[ix].y = 0;
	}
	for (int ix = 0; ix < b.len; ++ix) {
		NewPos[ix] = b.pos[ix];
	}
}

void MoveBody(Body& b) {
	for (int ix = b.len - 1; ix > 0; --ix) {
		b.pos[ix - 1] = NewPos[ix];
	}
	++count;
	PosCopy(b, NewPos);
}

void HideCursor() {
	CONSOLE_CURSOR_INFO info;
	GetConsoleCursorInfo(hMain_Out, &info);
	info.bVisible = FALSE;
	SetConsoleCursorInfo(hMain_Out, &info);
}

void CreateWall() {
	CONSOLE_SCREEN_BUFFER_INFO info;
	GetConsoleScreenBufferInfo(hMain_Out, &info);
	info.srWindow.Right -= 19;
	info.srWindow.Bottom -= 5;
	Wall = info.srWindow;
	for (int i = 0; i <= info.srWindow.Right; i += 2) {
		Print(i, info.srWindow.Top);
		Print(i, info.srWindow.Bottom);
	}
	for (int y = 0; y <= info.srWindow.Bottom; ++y) {
		Print(0, y);
		Print(info.srWindow.Right, y);
	}
}

void Print(int x, int y) {
	COORD c;
	c.X = x;
	c.Y = y;
	SetConsoleCursorPosition(hMain_Out, c);
	printf("■");
}

void CreateFood() {
	srand(unsigned(time(NULL)));
	unsigned x_t = RAND_MAX / Wall.Right;
	unsigned y_t = RAND_MAX / Wall.Bottom;
	while (true) {
		int x = rand() / x_t;
		int y = rand() / y_t;
		Food.x = x - 4;
		Food.y = y - 4;
		if ((0 == Food.x % 2) && (0 == Food.y % 2)) {
			if (Food.x < 5) {
				Food.x += 8;
			}
			if (Food.y < 5) {
				Food.y += 8;
			}
			Print(Food.x, Food.y);
			break;
		}
	}
}

bool IsKnock_Food(const Body& b) {
	if (b.pos[b.len - 1].x == Food.x && b.pos[b.len - 1].y == Food.y) {
		return true;
	}
	else {
		return false;
	}
}

bool IsKnock_Wall(const Body& b) {
	if (0 == b.pos[b.len - 1].x || 0 == b.pos[b.len - 1].y || Wall.Right == b.pos[b.len - 1].x || Wall.Bottom == b.pos[b.len - 1].y) {
		return true;
	}
	Pos Head = b.pos[b.len - 1];
	for (int ix = 0; ix <= b.len - 3; ++ix) {
		if (Head.x == b.pos[ix].x && Head.y == b.pos[ix].y) {
			return true;
		}
	}
	return false;
}

void ShowInfo() {
	COORD c;
	c.X = Wall.Right + 2;
	c.Y = 3;
	SetConsoleCursorPosition(hMain_Out, c);
	printf("  分数:%d", grade);
	c.Y += 10;
	SetConsoleCursorPosition(hMain_Out, c);
	printf("  难度等级:%d", level);

}

void AddBody(Body& b) {
	if (b.len < MAX) {
		if (UP == b.Direction) {
			b.pos[b.len].y = b.pos[b.len - 1].y - 1;
			b.pos[b.len].x = b.pos[b.len - 1].x;
			++b.len;
		}
		if (DOWN == b.Direction) {
			b.pos[b.len].y = b.pos[b.len - 1].y + 1;
			b.pos[b.len].x = b.pos[b.len - 1].x;
			++b.len;
		}
		if (LEFT == b.Direction) {
			b.pos[b.len].x = b.pos[b.len - 1].x - 2;
			b.pos[b.len].y = b.pos[b.len - 1].y;
			++b.len;
		}
		if (RIGHT == b.Direction) {
			b.pos[b.len].x = b.pos[b.len - 1].x + 2;
			b.pos[b.len].y = b.pos[b.len - 1].y;
			++b.len;
		}
	}
}

void HideTheCursor() {
	CONSOLE_CURSOR_INFO cciCursor;

	HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

	if (GetConsoleCursorInfo(hStdOut, &cciCursor)) {
		cciCursor.bVisible = FALSE;
		SetConsoleCursorInfo(hStdOut, &cciCursor);
		SetConsoleCursorInfo(hStdOut, &cciCursor);
	}
}

int main() {
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");

	basic();

	return 0;
}

六、技术亮点

随机生成食物位置:使用rand()函数和时间种子来生成随机数,确保食物位置的随机性。
碰撞检测:通过判断蛇头的位置是否与墙壁或食物的位置相同,以及蛇头是否与自身的其他部分重叠,来实现碰撞检测。
方向控制:根据用户按键输入来改变蛇的移动方向,实现了灵活的游戏操作。
难度提升:随着游戏的进行,食物数量增加,当达到一定数量时,提升游戏难度等级,加快蛇的移动速度。

七、改进建议

可以增加游戏音效,增强游戏的趣味性和沉浸感。
优化游戏界面,使用不同的字符或颜色来区分蛇身、食物和墙壁,提高游戏的视觉效果。
增加游戏模式选择,如单人模式、多人对战模式等,丰富游戏玩法。
对用户输入进行更严格的验证,防止输入错误导致程序异常。
可以将游戏数据保存到文件中,以便用户下次继续游戏。

九、特别鸣谢

特别感谢C++小盆友ta的主页)以及阳了个阳C++ta的主页)帮我测试游戏效果

相关推荐
网易独家音乐人Mike Zhou16 分钟前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
‘’林花谢了春红‘’1 小时前
C++ list (链表)容器
c++·链表·list
向宇it2 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
搬砖的小码农_Sky3 小时前
C语言:数组
c语言·数据结构
机器视觉知识推荐、就业指导3 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.995 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王5 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_5 小时前
C++自己写类 和 运算符重载函数
c++
六月的翅膀5 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++