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

news2024/11/15 18:07:14

在这里插入图片描述

在这里插入图片描述在这里插入图片描述

🚀欢迎互三👉:程序猿方梓燚 💎💎
🚀关注博主,后期持续更新系列文章
🚀如果有错误感谢请大家批评指出,及时修改
🚀感谢大家点赞👍收藏⭐评论✍

在这里插入图片描述

一、概述

本文对给定的贪吃蛇游戏代码进行详细分析。该游戏使用 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的主页)帮我测试游戏效果

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(第三期)书生大模型实战营——OpenXLab部署InternLM2实践——上传模型

OpenXLab 部署 InternLM2 实践指南 上传模型 初始化git设置 # install git sudo apt-get update sudo apt-get install git# install git lfs sudo apt-get update sudo apt-get install git-lfs# use git install lfs git lfs installOpenXLab 使用你在平台的用户名作为 Git…

【初阶数据结构】算法复杂度

目录 一、算法效率 1.1 为什么要衡量算法的好坏 1.2 算法的复杂度 1.3 复杂度在校招中的考察 二、时间复杂度 2.1 时间复杂度的概念 Func1 执行的基本操作次数 &#xff1a; 2.2 大O的渐进表示法 常见复杂度对比 一般算法常见的复杂度如下&#xff1a; ​编辑 2.3常…

SearXNG与LLM强强联合:打造用户隐私保护的智能搜索解答流程,隐私无忧,搜索无忧

SearXNG与LLM强强联合:打造用户隐私保护的智能搜索解答流程,隐私无忧,搜索无忧 SearXNG 是一个免费的互联网元搜索引擎,整合了各种搜索服务的结果。用户不会被跟踪,也不会被分析。 github地址:https://github.com/searxng/searxng 项目地址:https://docs.searxng.org/ 公…

尚品汇-网关过滤用户请求、登录流程(三十五)

目录&#xff1a; &#xff08;1&#xff09;用户认证与服务网关整合 &#xff08;2&#xff09;server-gateway网关配置 &#xff08;3&#xff09;在服务网关中判断用户登录状态 &#xff08;4&#xff09;登录流程 &#xff08;1&#xff09;用户认证与服务网关整合 实…

一对一私密发送分班结果,就用易查分

暑假即将结束&#xff0c;老师们又要开始忙碌起来&#xff0c;其中一项重要任务就是搭建一个分班信息查询系统。对于家长和学生来说&#xff0c;简直就是神器&#xff0c;因为它能提供快速便捷的查询服务。 想象一下&#xff0c;家长和学生通过扫描二维码&#xff0c;就能轻松…

【鸿蒙学习】HarmonyOS应用开发者基础 - 认证通过

考试时间&#xff1a;2024-08-20 22:00 考试结果&#xff1a;成功&#xff08;99分&#xff09; 一、开局叨叨 HarmonyOS应用开发者基础认证&#xff0c;不完美通过&#xff0c;考试分数99分&#xff0c;为啥不考100分。主要是当时没找到&#xff0c;后面通过录屏看了一下&am…

2024“华为杯”第二十一届中国研究生数学建模竞赛2004-2023华为杯数学建模优秀论文(见文末)

2024“华为杯”第二十一届中国研究生数学建模竞赛&2004-2023华为杯数学建模优秀论文&#xff08;见文末&#xff09; 各研究生培养单位&#xff1a; 中国研究生数学建模竞赛&#xff08;以下简称“竞赛”&#xff09;是教育部学位管理与研究生教育司指导&#xff0c;中国学…

TUIKit:chat-uikit-vue在微信中上传图片的一个bug

一、问题描述 今天使用腾讯IM的TUIKIT示例工程&#xff1a;https://github.com/TencentCloud/chat-uikit-vue.git &#xff0c;发现在Android设备的微信里打开&#xff0c;选择相册的图片进行编辑后发送会失败&#xff1a; 二、问题排查 由于在微信中不好排查问题&#xff…

docker 安装minio并配置https域名访问

一、准备目录 mkdir -p /home/minio/data/home/minio/config/home/minio/config/certs/二、下载域名证书&#xff0c;注意要Apache的 注意.key的换成 private.key&#xff0c;public.crt换成 public.crt&#xff0c;然后将这两个文件放到/home/minio/config/certs/目录下 三、…

微信小程序开发工具最新版版下载

最近&#xff0c;下载微信小程序开发工具的时候&#xff0c;因为网络问题&#xff0c;发现不能够直接下载&#xff0c;需要使用代理才行 如果你也遇到这个问题&#xff0c;可以通过网盘进行下载。 一、官网下载 下载地址&#xff1a;https://developers.weixin.qq.com/miniprog…

DC-1综合靶场渗透从外网打到内网,权限提升,入侵痕迹清除,干货,建议收藏

前言 博客主页&#xff1a;【h0ck1r丶羽】的公众号~~ 由于传播、利用本公众小羽网安提供的文章、工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;公众号小羽网安及作者不为此承担任何责任&#xff0c;一旦造成后果请自行承担&#xff01…

Jmeter进行http接口测试,这一篇就搞定

jmeter-http接口测试脚本 jmeter进行http接口测试的主要步骤&#xff08;1.添加线程组 2.添加http请求 3.在http请求中写入接口的URL&#xff0c;路径&#xff0c;请求方式&#xff0c;参数 4.添加查看结果树 5.调用接口&#xff0c;查看返回值&#xff09; 针对接口添加heade…

【数据分享】《云南省统计年鉴》(2000-2023)

而今天要限时免费分享的数据就是2000-2023年间出版的《云南省统计年鉴》并以多格式提供免费下载。&#xff08;无需分享朋友圈即可获取&#xff09; 数据介绍 在过去的二十多年里&#xff0c;云南省作为中国西南的重要门户&#xff0c;经历了翻天覆地的变化。这些变化不仅…

《机器学习》—— 通过下采样方法实现银行贷款分类问题

文章目录 一、什么是下采样方法&#xff1f;二、通过下采样方法实现银行贷款分类问题三、下采样的优缺点 一、什么是下采样方法&#xff1f; 机器学习中的下采样&#xff08;Undersampling&#xff09;方法是一种处理不平衡数据集的有效手段&#xff0c;特别是在数据集中某些类…

YUM和NFS

文章目录 yum软件仓库的提供方式RPM软件包的来源Linux系统各家厂商用的安装源命令---yum 配置本地yum源具体操作 搭建ftp yum仓库环境具体操作实操环境服务端一、安装 vsftpd服务二、创建一个文件&#xff0c;并且挂载三、开启服务四、查看挂载 客户端五、备份六、搭建ftp yum仓…

Word--两列(两栏、多栏、多列)文档中间插入横跨两列(多列)的表格

最终效果如下图所示 最终效果展示图 即&#xff1a;在原本是两列&#xff08;两栏、多栏、多列&#xff09;显示的Word文档中&#xff0c;插入一个横跨两列的表格&#xff0c;并且不影响文字排列&#xff0c;表格上侧的文字自动排序&#xff0c;表格下侧的文字自动排序&#…

JUC阻塞队列(三):PriorityBlockingQueue

1、PriorityBlockingQueue 介绍 PriorityBlockingQueue 是一个优先级队列&#xff0c;它不满足队列的先进先出特点&#xff1b; PriorityBlockingQueue 会对队列的数据进行排序&#xff0c;排序规则是数据的优先级&#xff1b; PriorityBlockingQueue是基于二叉堆来实现优先级的…

鸿蒙(API 12 Beta3版)【DRM会话管理(ArkTS)】数字版权保护

DRM会话管理&#xff08;MediaKeySession&#xff09;支持媒体密钥管理及媒体解密等&#xff0c;MediaKeySession实例由系统管理里的MediaKeySystem实例创建和销毁。 开发步骤 导入相关接口&#xff0c;导入方法如下。 import { drm } from kit.DrmKit;导入BusinessError模块&…

巡检机器人的使用方法和维护保养

在当今快速发展的工业环境中&#xff0c;智能巡检机器人正逐渐成为提升运维效率和安全性的重要工具。旗晟机器人凭借其核心技术团队和多年的行业经验&#xff0c;推出了多款高效、智能的巡检机器人&#xff0c;旨在帮助企业实现设备运维的智能化升级。本文将介绍旗晟巡检机器人…

第1章-02-Python环境安装与测试

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年CSDN全站百大博主。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已收录于专栏:Web爬虫入门与实战精讲。 🎉欢迎 👍点赞✍评论⭐收…