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 来获取控制台句柄、设置光标位置和隐藏光标等,增强了游戏的控制台界面效果。

四、代码详解

一、头文件详解

#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:提供标准输入输出函数。

二、全局变量详解

#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()函数

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()函数

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()函数

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()函数

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()函数

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()函数

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)函数

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)函数

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)函数

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)函数

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)函数

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)函数

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[])函数

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)函数

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()函数

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()函数

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()函数、

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)函数

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)函数

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()函数

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)函数

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,纵坐标与蛇尾位置相同,然后增加蛇的长度。

四、主函数详解

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()函数,进入游戏的基础功能模块,开始游戏的流程。

五、完整代码

#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的主页)帮我测试游戏效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/871371.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OpManager Plus简单说明以及在Linux下的安装

目录 1 简介2 安装2.1 Linux下安装 1 简介 OpManager Plus 属于ManageEngine&#xff0c;是一款商业软件。 ManageEngine OpManager是一款全面的网络监视软件&#xff0c;可为网络管理员提供集成控制台&#xff0c;用于管理路由器&#xff0c;防火墙&#xff0c;服务器&#x…

Datawhale AI 夏令营 第四期 AIGC Task3

活动简介 活动链接&#xff1a;Datawhale AI 夏令营&#xff08;第四期&#xff09; 以及AIGC里面的本次任务说明&#xff1a;Task 3 进阶上分-实战优化 这次任务呢&#xff0c;主要是对知识的一个讲解&#xff0c;包括ComfyUI工具的使用啊&#xff0c;以及LoRA的原理啊&…

ansible搭建+ansible常用模块

ansible搭建 管理机安装ansible,被管理节点必须打开ssh服务 1.管理机安装ansible yum -y install ansible 2.查看版本 ansible --version ansible 2.9.27 3.查找配置文件 find /etc/ -name "*ansible*" /etc/ansible /etc/ansible/ansible.cfg 4.三台被管理机…

在Windows上配置VSCode MinGW+CMake(包括C++多线程编程的两套API:posix和win32)

创建目录 首先&#xff0c;需要电脑上安装VSCode, 并且创建三个文件夹&#xff1a;cmake、MinGW-posix、MinGW-w32 文件下载 下载posix-seh posix和win32分别是c多线程变成的两套API,可根据不同需求安装&#xff0c;现在先下载配置环境需要的几个文件 百度搜索MinGW-64 点…

使用JavaScript解决reCAPTCHA:完整教程

虽然reCAPTCHA有效地保护了网络内容&#xff0c;但有时它也会妨碍合法活动&#xff0c;例如研究、数据分析或其他与合规相关的自动化任务&#xff0c;这些任务需要与网络服务进行交互。 你将学到什么 在本博客中&#xff0c;我们将带你逐步了解如何使用JavaScript解决reCAPTC…

C++:stack类(vector和list优缺点、deque)

目录 前言 数据结构 deque vector和list的优缺点 push pop top size empty 完整代码 前言 stack类就是数据结构中的栈 C数据结构&#xff1a;栈-CSDN博客 stack类所拥有的函数相比与string、vector和list类都少很多&#xff0c;这是因为栈这个数据结构是后进先出的…

[CSCCTF 2019 Qual]FlaskLight1

打开题目 右键查看一下源代码 看到提示&#xff0c;需要用GET方search函数

g6解决拓扑图中dagre布局需要增加同级节点的问题(旁挂层同级节点相连)

背景&#xff1a;dagre可以在节点数据中配置layer字段&#xff0c;为节点指定层级&#xff0c;但layer的指定不能违背图结构与层次布局的原则&#xff0c;也就是说每一条边的起点的layer一定小于终点的layer值&#xff0c;否则会导致布局失败。 解决办法&#xff1a;动态添加节…

嵌入式人工智能ESP32(4-PWM呼吸灯)

1、PWM基本原理 PWM&#xff08;Pulse-width modulation&#xff09;是脉冲宽度调制的缩写。脉冲宽度调制是一种模拟信号电平数字编码方法。脉冲宽度调制PWM是通过将有效的电信号分散成离散形式从而来降低电信号所传递的平均功率的一种方式。所以根据面积等效法则&#xff0c;…

超简单亿图图示安装教程/快速入门指南及快捷键大全

一、软件介绍 Edraw Max&#xff08;亿图图示&#xff09;作为一款全类型的图形图表设计软件&#xff0c;深受广大用户的欢迎。目前&#xff0c;Edraw Max&#xff08;亿图图示&#xff09;里拥有20000多个符号&#xff0c;有效地满足使用者的需求&#xff1b;另外&#xff0c;…

JDBC基础Demo

pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 …

STL六大组件

STL&#xff08;Standard Template Library&#xff0c;标准模板库&#xff09;是C标准库的一部分&#xff0c;提供了丰富且高效的数据结构和算法。STL主要由6大组件构成&#xff0c;分别是容器、算法、迭代器、适配器、仿函数和空间配置器。 容器&#xff08;Containers&#…

ES6 (一)——ES6 简介及环境搭建

目录 简介 环境搭建 可以在 Node.js 环境中运行 ES6 webpack 入口 (entry) loader 插件 (plugins) 利用 webpack 搭建应用 gulp 如何使用&#xff1f; 简介 ES6&#xff0c; 全称 ECMAScript 6.0 &#xff0c;是 JavaScript 的下一个版本标准&#xff0c;2015.06 发版…

ICC2:insertion delay会拉长同一skew group其他sink吗?

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 来自知识星球提问: 已知一个skew group包含若干sink,针对其中一个sink设置insertion delay,希望工具把它做长,命令如下: create_clock_skew_group -clock xx -objects {xx xx} -name sg set_cl…

2024 江苏省第二届数据安全技术应用职业技能竞赛 初赛 部分wp

文章目录 一、前言二、参考文章三、题目&#xff08;解析&#xff09;数据安全解题赛1、ds_0602&#xff08;30分&#xff09;2、333.file&#xff08;45分&#xff09;3、pf文件分析&#xff08;35分&#xff09;4、丢失的资料&#xff08;45分&#xff09;5、greatphp&#x…

C#学习之路day2

一、变量 用来在存储计算机当中存储数据 1、常见的数据类型&#xff0c; 2、声明变量的方式 *声明&#xff1a;变量类型 变量名 &#xff1b; *赋值&#xff1a;变量名 值 &#xff1b; 先声明 &#xff0c;再赋值 &#xff0c;再使用 int num ; //声明num 10; //赋值i…

ArcGIS Pro 实现人口分布栅格TIFF数据的网格提取与可视化

这里在分享一个人口1km精度栅格数据&#xff0c;LandScan是由美国能源部橡树岭国家实验室&#xff08;ORNL&#xff09;提供的全球人口分布数据集&#xff0c;具有最高分辨率的全球人口分布数据&#xff0c;是全球人口数据发布的社会标准&#xff0c;是全球最为准确、可靠&…

React原理之Fiber双缓冲

前置文章&#xff1a; React原理之 React 整体架构解读React原理之整体渲染流程React原理之Fiber详解 -----读懂这一篇需要对 React 整体架构和渲染流程有大致的概念 &#x1f60a;----- 在前面的文章中&#xff0c;简单介绍了 Fiber 架构&#xff0c;也了解了 Fiber 节点的…

macOS安装搭建python环境

安装Homebrew apt-get是一个常见于Debian和Ubuntu等基于Linux的操作系统中的包管理工具&#xff0c;用于安装、更新和移除软件包。然而&#xff0c;macOS使用的是Homebrew或者MacPorts等其他的包管理工具&#xff0c;并不使用apt-get。 如果你想在macOS上使用类似apt-get的功…

书生大模型实战营-进阶关-Lagent 自定义你的 Agent 智能体

Lagent 自定义你的 Agent 智能体 Lagent 介绍环境配置Lagent Web体验第1步&#xff0c;启动大模型API服务第2步&#xff0c;启动 Lagent 的 Web页面 基于 Lagent 自定义智能体 Lagent 介绍 Lagent 是一个轻量级、开源的基于大语言模型的智能体&#xff08;agent&#xff09;框…