🚀欢迎互三👉:雾狩 💎💎
🚀关注博主,后期持续更新系列文章
🚀如果有错误感谢请大家批评指出,及时修改
🚀感谢大家点赞👍收藏⭐评论✍
今天水一篇吧……😂老样子,先上效果
一、概述
本文将对“登录 + 贪吃蛇”游戏的代码进行详细剖析。该游戏实现了用户登录和注册功能,同时包含经典的贪吃蛇游戏玩法,还支持游戏存档和加载功能。代码使用了 C++ 语言,并结合了 Windows 操作系统的 API 函数,以实现窗口化的游戏界面和多媒体功能。
二、代码结构与功能模块划分
1. 头文件与命名空间
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <conio.h>
#include <cwchar>
#include <cctype>
#include <limits>
#include <stdio.h>
#include <cstdlib>
#include <ctime>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")using namespace std;
- 包含了多个标准库和 Windows 相关的头文件,以实现输入输出、文件操作、随机数生成、时间处理和多媒体功能。
using namespace std;
允许直接使用标准库中的类和函数。
2. 结构体定义
// User结构体用于存储用户的基本信息
struct User {string username;string password;
};// SaveData结构体用于存储游戏的存档信息
struct SaveData {string username;time_t playTime;int totalPlayTime;int length;int mode;vector<int> x;vector<int> y;int ax, ay;int dir;unsigned long long tk;
};
User
结构体用于存储用户的用户名和密码。SaveData
结构体用于存储游戏的存档信息,包括用户名、存档时间、总游戏时长、蛇的长度、游戏模式、蛇的坐标、苹果的坐标、蛇的移动方向和游戏时间戳。
3. 全局变量定义
vector<User> users;
vector<SaveData> saves;
const string DATA_FILE = "users.dat";
const string SAVE_FILE = "saves.dat";
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);#define SIZE 30
#define WIDTH 20
#define FPS 10
#define SPEED 3HWND m_hwnd;
int g_nWidth = WIDTH * SIZE + 10 + 300, g_nHeight = WIDTH * SIZE + 33;
int map[SIZE][SIZE];
std::vector<int> x, y;
int ax, ay;
int px, py;
int dir;
unsigned long long tk;
bool lock;
time_t startTime;
int currentMode;
int targetLength;
int currentSpeed;
string currentUsername;
bool isPaused = false;
bool isGameOver = false;
users
和saves
分别存储所有用户信息和游戏存档信息。DATA_FILE
和SAVE_FILE
分别是用户数据文件和游戏存档文件的名称。hConsole
用于控制控制台的输出。- 一系列宏定义了游戏地图的大小、方块宽度、帧率和初始速度。
- 其他全局变量用于存储游戏的各种状态和信息。
4. 辅助函数
4.1 时间转换函数
string getTimeStr(time_t t) {char buffer[80];struct tm* timeinfo = localtime(&t);strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo);return string(buffer);
}
- 将时间戳转换为字符串格式,方便显示存档时间。
4.2 音乐播放函数
void playBackgroundMusic() {mciSendString("close background_music", NULL, 0, NULL);mciSendString(R"(open "D:\Dev-cpp 作品\登录+贪吃蛇\Snake_music.mp3" type mpegvideo alias background_music)", NULL, 0, NULL);mciSendString("play background_music repeat", NULL, 0, NULL);isGameOver = false;
}void playVictoryMusic() {mciSendString("close background_music", NULL, 0, NULL);mciSendString("close fail_music", NULL, 0, NULL);mciSendString(R"(open "D:\Dev-cpp 作品\登录+贪吃蛇\Snake_victory.mp3" type mpegvideo alias victory_music)", NULL, 0, NULL);mciSendString("play victory_music", NULL, 0, NULL);isGameOver = true;
}void playFailMusic() {mciSendString("close background_music", NULL, 0, NULL);mciSendString("close victory_music", NULL, 0, NULL);mciSendString(R"(open "D:\Dev-cpp 作品\登录+贪吃蛇\Snake_fail.mp3" type mpegvideo alias fail_music)", NULL, 0, NULL);mciSendString("play fail_music", NULL, 0, NULL);isGameOver = true;
}void pauseAllMusic() {mciSendString("close background_music", NULL, 0, NULL);mciSendString("close victory_music", NULL, 0, NULL);mciSendString("close fail_music", NULL, 0, NULL);
}
- 分别用于播放背景音乐、胜利音乐、失败音乐和暂停所有音乐,使用
mciSendString
函数进行多媒体操作。
4.3 文件操作函数
bool fileExists(const string& filename) {ifstream file(filename);return file.good();
}bool loadUsers() {if (!fileExists(DATA_FILE)) {return true;}ifstream file(DATA_FILE, ios::binary);if (!file) {return false;}char header[4];file.read(header, 4);if (string(header, 4) != "USR1") {file.close();return false;}size_t count;file.read(reinterpret_cast<char*>(&count), sizeof(count));users.clear();for (size_t i = 0; i < count; i++) {User user;size_t len;file.read(reinterpret_cast<char*>(&len), sizeof(len));user.username.resize(len);file.read(&user.username[0], len);file.read(reinterpret_cast<char*>(&len), sizeof(len));user.password.resize(len);file.read(&user.password[0], len);users.push_back(user);}file.close();return true;
}bool saveUsers() {ofstream file(DATA_FILE, ios::binary);if (!file) {return false;}const char* header = "USR1";file.write(header, 4);size_t count = users.size();file.write(reinterpret_cast<const char*>(&count), sizeof(count));for (const auto& user : users) {size_t len = user.username.length();file.write(reinterpret_cast<const char*>(&len), sizeof(len));file.write(user.username.c_str(), len);len = user.password.length();file.write(reinterpret_cast<const char*>(&len), sizeof(len));file.write(user.password.c_str(), len);}file.close();return true;
}
fileExists
用于判断文件是否存在。loadUsers
用于加载用户数据,从二进制文件中读取用户信息。saveUsers
用于保存用户数据,将用户信息写入二进制文件。
4.4 用户操作函数
bool registerUser(const string& username, const string& password) {for (char c : username) {if (!isalnum(c) && c != '_' && c != '.') {return false;}}User newUser;newUser.username = username;newUser.password = password;users.push_back(newUser);return saveUsers();
}bool authenticateUser(const string& username, const string& password) {for (const auto& user : users) {if (user.username == username && user.password == password) {return true;}}return false;
}string getPasswordInput() {string password;char ch;while ((ch = _getch()) != '\r') {if (ch == '\b') {if (!password.empty()) {password.pop_back();cout << "\b \b";}} else {password += ch;cout << '*';}}cout << endl;return password;
}
registerUser
用于用户注册,验证用户名的合法性并将新用户信息保存到文件中。authenticateUser
用于用户登录验证,检查用户名和密码是否匹配。getPasswordInput
用于获取用户输入的密码,输入时显示星号保护隐私。
4.5 其他辅助函数
void showMessage(const string& message, const string& title, UINT style) {MessageBox(NULL, message.c_str(), title.c_str(), style);
}string trim(const string& str) {size_t first = str.find_first_not_of(" \t\n\r");if (first == string::npos)return "";size_t last = str.find_last_not_of(" \t\n\r");return str.substr(first, (last - first + 1));
}void setConsoleColor(WORD color) {SetConsoleTextAttribute(hConsole, color);
}void setConsoleCursorPosition(short x, short y) {COORD coord;coord.X = x;coord.Y = y;SetConsoleCursorPosition(hConsole, coord);
}
showMessage
用于显示消息框。trim
用于去除字符串前后的空格。setConsoleColor
用于设置控制台文本颜色。setConsoleCursorPosition
用于设置控制台光标位置。
5. 游戏核心函数
5.1 初始化函数
void init() {srand((unsigned)time(NULL));tk = 0;dir = 2;x.push_back(0);y.push_back(0);px = py = 0;ax = -1;memset(map, 0, sizeof(map));map[0][0] = 1;lock = false;isGameOver = false;
}
- 初始化游戏的各种状态,包括随机数种子、蛇的位置、苹果的位置和游戏地图等。
5.2 游戏结束处理函数
void gameover() {pauseAllMusic();playFailMusic();MessageBox(m_hwnd, "你输了", "游戏结束", MB_OK);x.clear();y.clear();init();startTime = time(NULL);
}void youwin() {pauseAllMusic();playVictoryMusic();MessageBox(m_hwnd, "你赢了", "游戏结束", MB_OK);x.clear();y.clear();init();startTime = time(NULL);
}
gameover
处理游戏失败的情况,播放失败音乐,显示消息框并重新初始化游戏。youwin
处理游戏胜利的情况,播放胜利音乐,显示消息框并重新初始化游戏。
5.3 蛇移动函数
void move(int d) {if (d == 0 && x[0] > 0) {x.insert(x.begin(), x[0] - 1);px = x[x.size() - 1];x.erase(x.begin() + x.size() - 1);y.insert(y.begin(), y[0]);py = y[y.size() - 1];y.erase(y.begin() + y.size() - 1);} else if (d == 0 && x[0] <= 0) gameover();else if (d == 2 && x[0] < SIZE - 1) {x.insert(x.begin(), x[0] + 1);px = x[x.size() - 1];x.erase(x.begin() + x.size() - 1);y.insert(y.begin(), y[0]);py = y[y.size() - 1];y.erase(y.begin() + y.size() - 1);} else if (d == 2 && x[0] >= SIZE - 1) gameover();else if (d == 1 && y[0] > 0) {x.insert(x.begin(), x[0]);px = x[x.size() - 1];x.erase(x.begin() + x.size() - 1);y.insert(y.begin(), y[0] - 1);py = y[y.size() - 1];y.erase(y.begin() + y.size() - 1);} else if (d == 1 && y[0] <= 0) gameover();else if (d == 3 && y[0] < SIZE - 1) {x.insert(x.begin(), x[0]);px = x[x.size() - 1];x.erase(x.begin() + x.size() - 1);y.insert(y.begin(), y[0] + 1);py = y[y.size() - 1];y.erase(y.begin() + y.size() - 1);} else if (d == 3 && y[0] >= SIZE - 1) gameover();
}
- 根据蛇的移动方向更新蛇的位置,如果蛇撞到边界则游戏失败。
5.4 游戏状态更新函数
void update() {if (isPaused || isGameOver) return;if (tk % currentSpeed == 0) {move(dir);lock = false;}if (x[0] == ax && y[0] == ay) {x.push_back(px);y.push_back(py);ax = -1;}if (currentMode != 5 && x.size() >= static_cast<size_t>(targetLength)) {youwin();return;}if (x.size() >= SIZE * SIZE) {youwin();return;}memset(map, 0, sizeof(map));for (size_t i = 0; i < x.size(); i++) {if (map[x[i]][y[i]] == 0) map[x[i]][y[i]] = 1;else {gameover();return;}}if (ax == -1) {ax = rand() % SIZE;ay = rand() % SIZE;while (map[ax][ay] == 1) {ax = rand() % SIZE;ay = rand() % SIZE;}}map[ax][ay] = 2;tk++;
}
- 更新游戏的状态,包括蛇的移动、吃苹果、判断游戏胜利或失败、生成新的苹果等。
5.5 窗口消息处理函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {switch (Message) {case WM_DESTROY: {PostQuitMessage(0);break;}case WM_KEYDOWN: {if (!lock && !isGameOver) {if (wParam == VK_UP && (dir - 0) % 2 != 0) {dir = 0;lock = true;} else if (wParam == VK_DOWN && (dir - 2) % 2 != 0) {dir = 2;lock = true;} else if (wParam == VK_LEFT && (dir - 1) % 2 != 0) {dir = 1;lock = true;} else if (wParam == VK_RIGHT && (dir - 3) % 2 != 0) {dir = 3;lock = true;}}if (wParam == VK_ESCAPE) {int result = MessageBox(hwnd, "确定要退出游戏吗?", "确认退出", MB_YESNO | MB_ICONQUESTION);if (result == IDYES) {PostQuitMessage(0);}}if (wParam == VK_SPACE) {isPaused = !isPaused;if (isPaused) {pauseAllMusic();} else {if (!isGameOver) {playBackgroundMusic();}}}if (wParam == VK_RETURN && isGameOver) {init();startTime = time(NULL);playBackgroundMusic();}break;}default:return DefWindowProc(hwnd, Message, wParam, lParam);}return 0;
}
- 处理窗口的各种消息,包括窗口销毁、按键按下等。根据不同的按键操作,更新蛇的移动方向、暂停或继续游戏、退出游戏等。
5.6 游戏画面渲染函数
void render() {HDC hDC = GetDC(m_hwnd);HDC memDC = CreateCompatibleDC(0);HBITMAP bmpBack = CreateCompatibleBitmap(hDC, g_nWidth, g_nHeight);SelectObject(memDC, bmpBack);HPEN penBack = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));SelectObject(memDC, penBack);HBRUSH brushBack = CreateSolidBrush(RGB(255, 255, 255));SelectObject(memDC, brushBack);RECT rcClient;GetClientRect(m_hwnd, &rcClient);FillRect(memDC, &rcClient, brushBack);HBRUSH brushHead = CreateSolidBrush(RGB(255, 0, 0));HBRUSH brushBody = CreateSolidBrush(RGB(0, 128, 0));HBRUSH brushApple = CreateSolidBrush(RGB(0, 0, 255));int dw = WIDTH;int rows = SIZE;int cols = SIZE;for (int r = 0; r < rows; ++r) {for (int c = 0; c < cols; ++c) {if (map[r][c] == 1) {if (r == x[0] && c == y[0]) {SelectObject(memDC, brushHead);} else {SelectObject(memDC, brushBody);}} else if (map[r][c] == 2) {SelectObject(memDC, brushApple);} else {SelectObject(memDC, brushBack);}Rectangle(memDC, c * dw, r * dw, (c + 1)*dw, (r + 1)*dw);}}time_t currentTime = time(NULL);int elapsedTime = static_cast<int>(currentTime - startTime);int hours = elapsedTime / 3600;int minutes = (elapsedTime % 3600) / 60;int seconds = elapsedTime % 60;char info[200];sprintf(info, "蛇的长度: %u\n游戏时长: %02d:%02d:%02d\n难度: %d\n目标长度: %d\n\n规则:\n",static_cast<unsigned>(x.size()), hours, minutes, seconds, currentMode, targetLength);switch (currentMode) {case 1:strcat(info, "简单模式:蛇的长度达到15即可通关。\n");break;case 2:strcat(info, "普通模式:蛇的长度达到30即可通关。\n");break;case 3:strcat(info, "困难模式:蛇的长度达到60即可通关。\n");break;case 4:strcat(info, "地狱模式:蛇的长度达到80即可通关。\n");break;case 5:strcat(info, "无尽模式:蛇占满全屏即可通关。\n");break;}strcat(info, "\n操作步骤:\n");strcat(info, "方向键:控制蛇的移动方向。\n");strcat(info, "Space键:暂停/继续游戏。\n");strcat(info, "Esc键:退出游戏。\n");if (isGameOver) {strcat(info, "\n游戏已结束,按Enter键重新开始。\n");}SetBkMode(memDC, TRANSPARENT);SetTextColor(memDC, RGB(0, 0, 0));RECT infoRect = {SIZE * WIDTH + 20, 20, g_nWidth - 20, g_nHeight - 20};DrawText(memDC, info, -1, &infoRect, DT_LEFT | DT_TOP);DeleteObject(brushHead);DeleteObject(brushBody);DeleteObject(brushApple);BitBlt(hDC, 0, 0, g_nWidth, g_nHeight, memDC, 0, 0, SRCCOPY);DeleteObject(penBack);DeleteObject(brushBack);DeleteObject(bmpBack);DeleteDC(memDC);ReleaseDC(m_hwnd, hDC);
}
- 渲染游戏画面,包括绘制蛇、苹果、游戏地图和显示游戏信息。使用双缓冲技术,避免画面闪烁。
5.7 游戏循环线程函数
DWORD WINAPI startLoop(LPVOID) {while (1) {update();render();Sleep(1000 / FPS);}return 0L;
}
- 游戏的主循环,不断更新游戏状态和渲染画面,通过
Sleep
函数控制游戏帧率。
6. 游戏模式和存档相关函数
6.1 选择游戏模式函数
int selectGameMode(const string&) {int mode;system("cls");setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 3);cout << "===== 选择游戏模式 =====" << endl;setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);setConsoleCursorPosition(15, 5);cout << "1. 简单模式 (长度达到15通关)" << endl;setConsoleCursorPosition(15, 6);cout << "2. 普通模式 (长度达到30通关)" << endl;setConsoleCursorPosition(15, 7);cout << "3. 困难模式 (长度达到60通关)" << endl;setConsoleCursorPosition(15, 8);cout << "4. 地狱模式 (长度达到80通关)" << endl;setConsoleCursorPosition(15, 9);cout << "5. 无尽模式 (占满全屏通关)" << endl;setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 11);cout << "=======================" << endl;setConsoleCursorPosition(15, 13);cout << "请选择: ";while (!(cin >> mode) || mode < 1 || mode > 5) {cin.clear();cin.ignore(numeric_limits<streamsize>::max(), '\n');showMessage("输入无效,请输入1 - 5之间的数字!", "错误", MB_ICONERROR);setConsoleCursorPosition(15, 13);cout << "请选择: ";}cin.ignore();return mode;
}
- 让用户选择游戏模式,根据用户输入返回相应的模式编号。
6.2 选择游戏存档函数
int selectSaveGame(const string& username) {vector<SaveData> userSaves;if (fileExists(SAVE_FILE)) {ifstream file(SAVE_FILE, ios::binary);if (file) {char header[4];file.read(header, 4);if (string(header, 4) == "SAV1") {size_t count;file.read(reinterpret_cast<char*>(&count), sizeof(count));saves.clear();for (size_t i = 0; i < count; i++) {SaveData save;size_t len;file.read(reinterpret_cast<char*>(&len), sizeof(len));save.username.resize(len);file.read(&save.username[0], len);file.read(reinterpret_cast<char*>(&save.playTime), sizeof(save.playTime));file.read(reinterpret_cast<char*>(&save.totalPlayTime), sizeof(save.totalPlayTime));file.read(reinterpret_cast<char*>(&save.length), sizeof(save.length));file.read(reinterpret_cast<char*>(&save.mode), sizeof(save.mode));file.read(reinterpret_cast<char*>(&len), sizeof(len));save.x.resize(len);for (int& val : save.x) {file.read(reinterpret_cast<char*>(&val), sizeof(val));}file.read(reinterpret_cast<char*>(&len), sizeof(len));save.y.resize(len);for (int& val : save.y) {file.read(reinterpret_cast<char*>(&val), sizeof(val));}file.read(reinterpret_cast<char*>(&save.ax), sizeof(save.ax));file.read(reinterpret_cast<char*>(&save.ay), sizeof(save.ay));file.read(reinterpret_cast<char*>(&save.dir), sizeof(save.dir));file.read(reinterpret_cast<char*>(&save.tk), sizeof(save.tk));saves.push_back(save);}}file.close();}}for (const auto& save : saves) {if (save.username == username) {userSaves.push_back(save);}}if (userSaves.empty()) {showMessage("没有可用的存档,将开始新游戏。", "提示", MB_ICONINFORMATION);return -1;}system("cls");setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 3);cout << "===== 选择存档 =====" << endl;for (size_t i = 0; i < userSaves.size(); i++) {setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);setConsoleCursorPosition(15, static_cast<short>(5 + i));cout << i + 1 << ". 存档时间: " << getTimeStr(userSaves[i].playTime)<< " 游玩总时长: " << userSaves[i].totalPlayTime << " 秒"<< " 长度: " << userSaves[i].length<< " 模式: " << userSaves[i].mode << endl;}setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, static_cast<short>(6 + userSaves.size()));cout << "0. 开始新游戏" << endl;setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, static_cast<short>(8 + userSaves.size()));cout << "=======================" << endl;setConsoleCursorPosition(15, static_cast<short>(10 + userSaves.size()));cout << "请选择: ";int choice;while (!(cin >> choice) || choice < 0 || static_cast<size_t>(choice) > userSaves.size()) {cin.clear();cin.ignore(numeric_limits<streamsize>::max(), '\n');showMessage("输入无效,请输入正确的存档编号!", "错误", MB_ICONERROR);setConsoleCursorPosition(15, static_cast<short>(10 + userSaves.size()));cout << "请选择: ";}cin.ignore();if (choice == 0) {return -1;} else {x = userSaves[choice - 1].x;y = userSaves[choice - 1].y;ax = userSaves[choice - 1].ax;ay = userSaves[choice - 1].ay;dir = userSaves[choice - 1].dir;tk = userSaves[choice - 1].tk;currentMode = userSaves[choice - 1].mode;startTime = time(NULL) - userSaves[choice - 1].totalPlayTime;switch (currentMode) {case 1: targetLength = 15; currentSpeed = 5; break;case 2: targetLength = 30; currentSpeed = 3; break;case 3: targetLength = 60; currentSpeed = 2; break;case 4: targetLength = 80; currentSpeed = 1; break;case 5: targetLength = SIZE * SIZE; currentSpeed = 3; break;}return choice - 1;}
}
- 让用户选择游戏存档,如果有可用存档,显示存档信息供用户选择;如果没有可用存档,提示用户开始新游戏。选择存档后,加载存档信息。
6.3 保存游戏存档函数
void saveGame(const string& username) {int result = MessageBox(m_hwnd, "是否要保存存档?", "保存存档", MB_YESNO | MB_ICONQUESTION);if (result == IDYES) {time_t currentTime = time(NULL);int elapsedTime = static_cast<int>(currentTime - startTime);SaveData save;save.username = username;save.playTime = currentTime;save.totalPlayTime = elapsedTime;save.length = static_cast<int>(x.size());save.mode = currentMode;save.x = x;save.y = y;save.ax = ax;save.ay = ay;save.dir = dir;save.tk = tk;for (auto it = saves.begin(); it != saves.end(); ) {if (it->username == username) {it = saves.erase(it);} else {++it;}}saves.push_back(save);// 后续应添加将saves保存到文件的代码}
}
- 询问用户是否保存存档,如果用户选择是,创建一个新的存档对象并保存到
saves
向量中,同时删除该用户的旧存档。
三、完整代码
下载地址
解压后,跟着操作步骤.txt
下载源代码(包含有音乐和无音乐版本)
无音乐版本的代码如下,大家可以先试着玩玩看
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <conio.h>
#include <cwchar>
#include <cctype>
#include <limits>
#include <stdio.h>
#include <cstdlib>
#include <ctime>using namespace std;// 用户结构
struct User {string username;string password;
};// 存档结构
struct SaveData {string username;time_t playTime;int totalPlayTime;int length;int mode;vector<int> x;vector<int> y;int ax, ay;int dir;unsigned long long tk;
};// 全局变量
vector<User> users;
vector<SaveData> saves;
const string DATA_FILE = "users.dat";
const string SAVE_FILE = "saves.dat";
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); // 控制台句柄// 贪吃蛇相关全局变量
#define SIZE 30 // 指定游戏地图大小为30*30
#define WIDTH 20 // 指定每个格子的大小
#define FPS 10 // 每秒刷新多少次
#define SPEED 3 // 每刷新多少次蛇移动一格HWND m_hwnd; // 窗口句柄,唯一标示游戏窗口
int g_nWidth = WIDTH * SIZE + 10 + 300, g_nHeight = WIDTH * SIZE + 33; // 指定窗口大小,增加右侧信息栏宽度
int map[SIZE][SIZE]; // 地图数组,值为0表示空,1表示蛇身,2表示苹果
std::vector<int> x, y; // 用于保存蛇身体每一个部位的位置
int ax, ay; // 苹果的位置
int px, py; // 最后一次蛇经过的位置,用于吃过苹果后加长蛇身
int dir; // 表明当前方向
unsigned long long tk; // 游戏ticket数,即已经过多少逻辑帧
bool lock; // 操作锁,防止一次刷新操作两次
time_t startTime; // 游戏开始时间
int currentMode; // 当前游戏模式
int targetLength; // 目标长度
int currentSpeed; // 当前游戏速度
string currentUsername; // 当前登录用户名
bool isPaused = false; // 暂停标志// 函数声明
bool loadUsers();
bool saveUsers();
bool registerUser(const string& username, const string& password);
bool authenticateUser(const string& username, const string& password);
string getPasswordInput();
void showMessage(const string& message, const string& title = "消息", UINT style = MB_OK);
bool fileExists(const string& filename);
string trim(const string& str);
void setConsoleColor(WORD color);
void setConsoleCursorPosition(short x, short y);
void init(); // 初始化参数
void gameover(); // 输了时调用
void youwin(); // 赢了时调用
void move(int d); // 调用次函数以移动
void update(); // 游戏更新主逻辑,每帧调用此函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
void render(); // 渲染函数,用于绘制游戏图像
DWORD WINAPI startLoop(LPVOID lpParamter); // 开始主循环
int selectGameMode(const string& username);
int selectSaveGame(const string& username);
void saveGame(const string& username);
string getTimeStr(time_t t);int main() {// 初始化:加载用户数据if (!loadUsers()) {showMessage("无法加载用户数据!程序将使用空用户数据库。", "错误", MB_ICONERROR);}int choice;bool exitProgram = false;bool loggedIn = false;while (!exitProgram) {system("cls"); // 清屏// 设置标题颜色为青色setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 3);cout << "===== 用户管理系统 =====" << endl;// 设置菜单选项颜色为白色setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);setConsoleCursorPosition(18, 5);cout << "1. 登录" << endl;setConsoleCursorPosition(18, 6);cout << "2. 注册" << endl;setConsoleCursorPosition(18, 7);cout << "0. 退出" << endl;setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 9);cout << "=======================" << endl;setConsoleCursorPosition(15, 11);cout << "请选择: ";while (!(cin >> choice)) {cin.clear();cin.ignore(numeric_limits<streamsize>::max(), '\n');showMessage("输入无效,请输入数字选项!", "错误", MB_ICONERROR);setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);setConsoleCursorPosition(15, 11);cout << "请选择: ";}cin.ignore(); // 清除输入缓冲区switch (choice) {case 1: { // 登录string username, password;setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);setConsoleCursorPosition(15, 14);cout << "用户名: ";getline(cin, username);username = trim(username);setConsoleCursorPosition(15, 15);cout << "密码: ";password = getPasswordInput();password = trim(password);if (authenticateUser(username, password)) {string welcomeMsg = "用户" + username + "欢迎您";showMessage(welcomeMsg, "欢迎", MB_ICONINFORMATION);loggedIn = true;currentUsername = username;exitProgram = true;} else {showMessage("用户名或密码错误!", "错误", MB_ICONERROR);system("pause");}break;}case 2: { // 注册string username, password, confirmPassword;bool registered = false;while (!registered) {system("cls");setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 3);cout << "===== 用户注册 =====" << endl;setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);setConsoleCursorPosition(15, 5);cout << "新用户名: ";getline(cin, username);username = trim(username);if (username.empty()) {showMessage("用户名不能为空!", "错误", MB_ICONERROR);system("pause");continue;}bool userExists = false;for (const auto& user : users) {if (user.username == username) {userExists = true;break;}}if (userExists) {showMessage("用户名已存在!", "错误", MB_ICONERROR);system("pause");continue;}bool validPassword = false;while (!validPassword) {setConsoleCursorPosition(15, 7);cout << "密码(至少6个字符): ";password = getPasswordInput();password = trim(password);if (password.length() < 6) {showMessage("密码长度至少需要6个字符!请重新输入。", "提示", MB_ICONINFORMATION);system("pause");continue;}setConsoleCursorPosition(15, 8);cout << "确认密码: ";confirmPassword = getPasswordInput();confirmPassword = trim(confirmPassword);if (password != confirmPassword) {showMessage("两次输入的密码不匹配!请重新输入。", "错误", MB_ICONERROR);system("pause");continue;}validPassword = true;}if (registerUser(username, password)) {showMessage("注册成功!请使用新账户登录。", "成功", MB_ICONINFORMATION);registered = true;system("pause");} else {showMessage("注册失败!可能是用户名包含非法字符。", "错误", MB_ICONERROR);system("pause");}}break;}case 0: // 退出setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 14);cout << "感谢使用!再见。" << endl;Sleep(2000);system("cls");cout << "正在退出...";Sleep(1000);exitProgram = true;break;default:showMessage("无效的选择,请重试!", "错误", MB_ICONERROR);system("pause");}}if (loggedIn) {int saveIndex = selectSaveGame(currentUsername);if (saveIndex == -1) {currentMode = selectGameMode(currentUsername);}switch (currentMode) {case 1: targetLength = 15; currentSpeed = 5; break;case 2: targetLength = 30; currentSpeed = 3; break;case 3: targetLength = 60; currentSpeed = 2; break;case 4: targetLength = 80; currentSpeed = 1; break;case 5: targetLength = SIZE * SIZE; currentSpeed = 3; break;}WNDCLASSEX wc; /* A properties struct of our window */HWND hwnd; /* A 'HANDLE', hence the H, or a pointer to our window */MSG msg; /* A temporary location for all messages *//* zero out the struct and set the stuff we want to modify */memset(&wc, 0, sizeof(wc));wc.cbSize = sizeof(WNDCLASSEX);wc.lpfnWndProc = WndProc; /* This is where we will send messages to */wc.hInstance = GetModuleHandle(NULL);wc.hCursor = LoadCursor(NULL, IDC_ARROW);/* White, COLOR_WINDOW is just a #define for a system color, try Ctrl+Clicking it */wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);wc.lpszClassName = "WindowClass";wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); /* Load a standard icon */wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); /* use the name "A" to use the project icon */if (!RegisterClassEx(&wc)) {MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);return 0;}hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, "WindowClass", "贪吃蛇游戏,按ESC退出", WS_VISIBLE | WS_EX_STATICEDGE,CW_USEDEFAULT, /* x */CW_USEDEFAULT, /* y */g_nWidth, /* width */g_nHeight, /* height */NULL, NULL, GetModuleHandle(NULL), NULL);if (hwnd == NULL) {MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK);return 0;}m_hwnd = hwnd;init(); // 初始化startTime = time(NULL); // 记录游戏开始时间HANDLE hThread = CreateThread(NULL, 0, startLoop, NULL, 0, NULL); // 创建线程CloseHandle(hThread); // 释放线程句柄/*This is the heart of our program where all input is processed andsent to WndProc. Note that GetMessage blocks code flow until it receives something, sothis loop will not produce unreasonably high CPU usage*/while (GetMessage(&msg, NULL, 0, 0) > 0) { /* If no error is received... */TranslateMessage(&msg); /* Translate key codes to chars if present */DispatchMessage(&msg); /* Send it to WndProc */}saveGame(currentUsername);return msg.wParam;}return 0;
}// 检查文件是否存在
bool fileExists(const string& filename) {ifstream file(filename);return file.good();
}// 从文件加载用户数据
bool loadUsers() {if (!fileExists(DATA_FILE)) {return true;}ifstream file(DATA_FILE, ios::binary);if (!file) {return false;}char header[4];file.read(header, 4);if (string(header, 4) != "USR1") {file.close();return false;}size_t count;file.read(reinterpret_cast<char*>(&count), sizeof(count));users.clear();for (size_t i = 0; i < count; i++) {User user;size_t len;file.read(reinterpret_cast<char*>(&len), sizeof(len));user.username.resize(len);file.read(&user.username[0], len);file.read(reinterpret_cast<char*>(&len), sizeof(len));user.password.resize(len);file.read(&user.password[0], len);users.push_back(user);}file.close();return true;
}// 保存用户数据到文件
bool saveUsers() {ofstream file(DATA_FILE, ios::binary);if (!file) {return false;}const char* header = "USR1";file.write(header, 4);size_t count = users.size();file.write(reinterpret_cast<const char*>(&count), sizeof(count));for (const auto& user : users) {size_t len = user.username.length();file.write(reinterpret_cast<const char*>(&len), sizeof(len));file.write(user.username.c_str(), len);len = user.password.length();file.write(reinterpret_cast<const char*>(&len), sizeof(len));file.write(user.password.c_str(), len);}file.close();return true;
}// 注册新用户
bool registerUser(const string& username, const string& password) {for (char c : username) {if (!isalnum(c) && c != '_' && c != '.') {return false;}}User newUser;newUser.username = username;newUser.password = password;users.push_back(newUser);return saveUsers();
}// 验证用户
bool authenticateUser(const string& username, const string& password) {for (const auto& user : users) {if (user.username == username && user.password == password) {return true;}}return false;
}// 获取密码输入(隐藏显示)
string getPasswordInput() {string password;char ch;while ((ch = _getch()) != '\r') {if (ch == '\b') {if (!password.empty()) {password.pop_back();cout << "\b \b";}} else {password += ch;cout << '*';}}cout << endl;return password;
}// 显示消息对话框
void showMessage(const string& message, const string& title, UINT style) {MessageBox(NULL, message.c_str(), title.c_str(), style);
}// 去除字符串首尾空格
string trim(const string& str) {size_t first = str.find_first_not_of(" \t\n\r");if (first == string::npos)return "";size_t last = str.find_last_not_of(" \t\n\r");return str.substr(first, (last - first + 1));
}// 设置控制台文字颜色
void setConsoleColor(WORD color) {SetConsoleTextAttribute(hConsole, color);
}// 设置控制台光标位置
void setConsoleCursorPosition(short x, short y) {COORD coord;coord.X = x;coord.Y = y;SetConsoleCursorPosition(hConsole, coord);
}// 初始化参数
void init() {srand((unsigned)time(NULL));tk = 0;dir = 2;x.push_back(0);y.push_back(0);px = py = 0;ax = -1;memset(map, 0, sizeof(map));map[0][0] = 1;lock = false;
}// 输了时调用
void gameover() {MessageBox(m_hwnd, "你输了", "游戏结束", MB_OK); // 弹窗x.clear(); // 重新初始化y.clear();init();startTime = time(NULL); // 重置游戏开始时间
}// 赢了时调用
void youwin() {MessageBox(m_hwnd, "你赢了", "游戏结束", MB_OK);x.clear();y.clear();init();startTime = time(NULL); // 重置游戏开始时间
}// 调用次函数以移动
void move(int d) {if (d == 0 && x[0] > 0) { // 当移动方向为上并且移动合法时,保存并去掉蛇尾,加一格蛇头x.insert(x.begin(), x[0] - 1);px = x[x.size() - 1];x.erase(x.begin() + x.size() - 1);y.insert(y.begin(), y[0]);py = y[y.size() - 1];y.erase(y.begin() + y.size() - 1);} else if (d == 0 && x[0] <= 0) gameover(); // 当移动不合法时游戏结束else if (d == 2 && x[0] < SIZE - 1) {x.insert(x.begin(), x[0] + 1);px = x[x.size() - 1];x.erase(x.begin() + x.size() - 1);y.insert(y.begin(), y[0]);py = y[y.size() - 1];y.erase(y.begin() + y.size() - 1);} else if (d == 2 && x[0] >= SIZE - 1) gameover();else if (d == 1 && y[0] > 0) {x.insert(x.begin(), x[0]);px = x[x.size() - 1];x.erase(x.begin() + x.size() - 1);y.insert(y.begin(), y[0] - 1);py = y[y.size() - 1];y.erase(y.begin() + y.size() - 1);} else if (d == 1 && y[0] <= 0) gameover();else if (d == 3 && y[0] < SIZE - 1) {x.insert(x.begin(), x[0]);px = x[x.size() - 1];x.erase(x.begin() + x.size() - 1);y.insert(y.begin(), y[0] + 1);py = y[y.size() - 1];y.erase(y.begin() + y.size() - 1);} else if (d == 3 && y[0] >= SIZE - 1) gameover();
}// 游戏更新主逻辑,每帧调用此函数
void update() {if (isPaused) return; // 如果暂停,不进行更新if (tk % currentSpeed == 0) { // 每隔currentSpeed间隔帧移动一次move(dir);lock = false;}if (x[0] == ax && y[0] == ay) { // 如果吃到了苹果x.push_back(px); // 加蛇尾y.push_back(py);ax = -1; // 去掉苹果}if (currentMode != 5 && x.size() >= static_cast<size_t>(targetLength)) { // 如果达到目标长度,除无尽模式youwin();return;}if (x.size() >= SIZE * SIZE) { // 如果蛇的长度大于等于地图大小,游戏结束youwin();return;}memset(map, 0, sizeof(map)); // 刷新地图for (size_t i = 0; i < x.size(); i++) {if (map[x[i]][y[i]] == 0) map[x[i]][y[i]] = 1; // 如果没有蛇身阻挡则放置蛇身else { // 否则游戏结束gameover();return;}}if (ax == -1) { // 苹果被吃,刷新苹果ax = rand() % SIZE;ay = rand() % SIZE;while (map[ax][ay] == 1) {ax = rand() % SIZE;ay = rand() % SIZE;}}map[ax][ay] = 2;tk++;
}/* 此函数用于处理窗口接受的所有消息 */
LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {switch (Message) {/* Upon destruction, tell the main thread to stop */case WM_DESTROY: {PostQuitMessage(0);break;}case WM_KEYDOWN: { // 当收到按键消息if (!lock) { // 且操作锁没有锁上,则改变蛇的移动方向并上锁if (wParam == VK_UP && (dir - 0) % 2 != 0) {dir = 0;lock = true;} else if (wParam == VK_DOWN && (dir - 2) % 2 != 0) {dir = 2;lock = true;} else if (wParam == VK_LEFT && (dir - 1) % 2 != 0) {dir = 1;lock = true;} else if (wParam == VK_RIGHT && (dir - 3) % 2 != 0) {dir = 3;lock = true;}}if (wParam == VK_ESCAPE) {int result = MessageBox(hwnd, "确定要退出游戏吗?", "确认退出", MB_YESNO | MB_ICONQUESTION);if (result == IDYES) {PostQuitMessage(0);}}if (wParam == VK_SPACE) {isPaused = !isPaused; // 切换暂停状态}break;}/* All other messages (a lot of them) are processed using default procedures */default:return DefWindowProc(hwnd, Message, wParam, lParam);}return 0;
}// 渲染函数,用于绘制游戏图像
void render() {HDC hDC = GetDC(m_hwnd); // 定义窗口句柄的DCHDC memDC = CreateCompatibleDC(0); // 定义兼容DCHBITMAP bmpBack = CreateCompatibleBitmap(hDC, g_nWidth, g_nHeight); // 定义位图画布SelectObject(memDC, bmpBack);HPEN penBack = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); // 定义画笔SelectObject(memDC, penBack);HBRUSH brushBack = CreateSolidBrush(RGB(255, 255, 255)); // 定义背景画刷SelectObject(memDC, brushBack);RECT rcClient;GetClientRect(m_hwnd, &rcClient);FillRect(memDC, &rcClient, brushBack); // 用背景画刷以矩形填充整个窗口HBRUSH brushHead = CreateSolidBrush(RGB(255, 0, 0)); // 定义蛇头画刷 (红色)HBRUSH brushBody = CreateSolidBrush(RGB(0, 128, 0)); // 定义蛇身画刷 (深绿色)HBRUSH brushApple = CreateSolidBrush(RGB(0, 0, 255)); // 定义苹果画刷 (蓝色)int dw = WIDTH;int rows = SIZE;int cols = SIZE;for (int r = 0; r < rows; ++r) { // 绘制整个矩阵for (int c = 0; c < cols; ++c) {if (map[r][c] == 1) {if (r == x[0] && c == y[0]) {SelectObject(memDC, brushHead); // 蛇头使用红色} else {SelectObject(memDC, brushBody); // 蛇身使用深绿色}} else if (map[r][c] == 2) {SelectObject(memDC, brushApple); // 苹果使用蓝色} else {SelectObject(memDC, brushBack); // 背景使用白色}Rectangle(memDC, c * dw, r * dw, (c + 1)*dw, (r + 1)*dw);}}// 绘制蛇的长度和游戏时长time_t currentTime = time(NULL);int elapsedTime = static_cast<int>(currentTime - startTime);int hours = elapsedTime / 3600;int minutes = (elapsedTime % 3600) / 60;int seconds = elapsedTime % 60;char info[200];sprintf(info, "蛇的长度: %zu\n游戏时长: %02d:%02d:%02d\n难度: %d\n目标长度: %d\n\n规则:\n",x.size(), hours, minutes, seconds, currentMode, targetLength);switch (currentMode) {case 1:strcat(info, "简单模式:蛇的长度达到15即可通关。\n");break;case 2:strcat(info, "普通模式:蛇的长度达到30即可通关。\n");break;case 3:strcat(info, "困难模式:蛇的长度达到60即可通关。\n");break;case 4:strcat(info, "地狱模式:蛇的长度达到80即可通关。\n");break;case 5:strcat(info, "无尽模式:蛇占满全屏即可通关。\n");break;}strcat(info, "\n操作步骤:\n");strcat(info, "方向键:控制蛇的移动方向。\n");strcat(info, "Space键:暂停/继续游戏。\n");strcat(info, "Esc键:退出游戏。\n");SetBkMode(memDC, TRANSPARENT);SetTextColor(memDC, RGB(0, 0, 0));RECT infoRect = {SIZE * WIDTH + 20, 20, g_nWidth - 20, g_nHeight - 20};DrawText(memDC, info, -1, &infoRect, DT_LEFT | DT_TOP);DeleteObject(brushHead);DeleteObject(brushBody);DeleteObject(brushApple);BitBlt(hDC, 0, 0, g_nWidth, g_nHeight, memDC, 0, 0, SRCCOPY);DeleteObject(penBack);DeleteObject(brushBack);DeleteObject(bmpBack);DeleteDC(memDC);ReleaseDC(m_hwnd, hDC);
}// 开始主循环,定义这个函数是为了多线程运行,函数名随意,函数格式一定
DWORD WINAPI startLoop(LPVOID /*lpParamter*/) {while (1) {update();render();Sleep(1000 / FPS);}return 0L;
}// 选择游戏模式
int selectGameMode(const string& /*username*/) {int mode;system("cls");setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 3);cout << "===== 选择游戏模式 =====" << endl;setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);setConsoleCursorPosition(15, 5);cout << "1. 简单模式 (长度达到15通关)" << endl;setConsoleCursorPosition(15, 6);cout << "2. 普通模式 (长度达到30通关)" << endl;setConsoleCursorPosition(15, 7);cout << "3. 困难模式 (长度达到60通关)" << endl;setConsoleCursorPosition(15, 8);cout << "4. 地狱模式 (长度达到80通关)" << endl;setConsoleCursorPosition(15, 9);cout << "5. 无尽模式 (占满全屏通关)" << endl;setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 11);cout << "=======================" << endl;setConsoleCursorPosition(15, 13);cout << "请选择: ";while (!(cin >> mode) || mode < 1 || mode > 5) {cin.clear();cin.ignore(numeric_limits<streamsize>::max(), '\n');showMessage("输入无效,请输入1 - 5之间的数字!", "错误", MB_ICONERROR);setConsoleCursorPosition(15, 13);cout << "请选择: ";}cin.ignore(); // 清除输入缓冲区return mode;
}// 选择存档
int selectSaveGame(const string& username) {vector<SaveData> userSaves;if (fileExists(SAVE_FILE)) {ifstream file(SAVE_FILE, ios::binary);if (file) {char header[4];file.read(header, 4);if (string(header, 4) == "SAV1") {size_t count;file.read(reinterpret_cast<char*>(&count), sizeof(count));saves.clear();for (size_t i = 0; i < count; i++) {SaveData save;size_t len;file.read(reinterpret_cast<char*>(&len), sizeof(len));save.username.resize(len);file.read(&save.username[0], len);file.read(reinterpret_cast<char*>(&save.playTime), sizeof(save.playTime));file.read(reinterpret_cast<char*>(&save.totalPlayTime), sizeof(save.totalPlayTime));file.read(reinterpret_cast<char*>(&save.length), sizeof(save.length));file.read(reinterpret_cast<char*>(&save.mode), sizeof(save.mode));file.read(reinterpret_cast<char*>(&len), sizeof(len));save.x.resize(len);for (int& val : save.x) {file.read(reinterpret_cast<char*>(&val), sizeof(val));}file.read(reinterpret_cast<char*>(&len), sizeof(len));save.y.resize(len);for (int& val : save.y) {file.read(reinterpret_cast<char*>(&val), sizeof(val));}file.read(reinterpret_cast<char*>(&save.ax), sizeof(save.ax));file.read(reinterpret_cast<char*>(&save.ay), sizeof(save.ay));file.read(reinterpret_cast<char*>(&save.dir), sizeof(save.dir));file.read(reinterpret_cast<char*>(&save.tk), sizeof(save.tk));saves.push_back(save);}}file.close();}}for (const auto& save : saves) {if (save.username == username) {userSaves.push_back(save);}}if (userSaves.empty()) {showMessage("没有可用的存档,将开始新游戏。", "提示", MB_ICONINFORMATION);return -1;}system("cls");setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, 3);cout << "===== 选择存档 =====" << endl;for (size_t i = 0; i < userSaves.size(); i++) {setConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);setConsoleCursorPosition(15, static_cast<short>(5 + i));cout << i + 1 << ". 存档时间: " << getTimeStr(userSaves[i].playTime)<< " 游玩总时长: " << userSaves[i].totalPlayTime << " 秒"<< " 长度: " << userSaves[i].length<< " 模式: " << userSaves[i].mode << endl;}setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, static_cast<short>(6 + userSaves.size()));cout << "0. 开始新游戏" << endl;setConsoleColor(FOREGROUND_BLUE | FOREGROUND_GREEN);setConsoleCursorPosition(15, static_cast<short>(8 + userSaves.size()));cout << "=======================" << endl;setConsoleCursorPosition(15, static_cast<short>(10 + userSaves.size()));cout << "请选择: ";int choice;while (!(cin >> choice) || choice < 0 || static_cast<size_t>(choice) > userSaves.size()) {cin.clear();cin.ignore(numeric_limits<streamsize>::max(), '\n');showMessage("输入无效,请输入正确的存档编号!", "错误", MB_ICONERROR);setConsoleCursorPosition(15, static_cast<short>(10 + userSaves.size()));cout << "请选择: ";}cin.ignore(); // 清除输入缓冲区if (choice == 0) {return -1;} else {// 加载存档逻辑x = userSaves[choice - 1].x;y = userSaves[choice - 1].y;ax = userSaves[choice - 1].ax;ay = userSaves[choice - 1].ay;dir = userSaves[choice - 1].dir;tk = userSaves[choice - 1].tk;currentMode = userSaves[choice - 1].mode;startTime = time(NULL) - userSaves[choice - 1].totalPlayTime;switch (currentMode) {case 1: targetLength = 15; currentSpeed = 5; break;case 2: targetLength = 30; currentSpeed = 3; break;case 3: targetLength = 60; currentSpeed = 2; break;case 4: targetLength = 80; currentSpeed = 1; break;case 5: targetLength = SIZE * SIZE; currentSpeed = 3; break;}return choice - 1;}
}// 保存游戏
void saveGame(const string& username) {int result = MessageBox(m_hwnd, "是否要保存存档?", "保存存档", MB_YESNO | MB_ICONQUESTION);if (result == IDYES) {time_t currentTime = time(NULL);int elapsedTime = static_cast<int>(currentTime - startTime);SaveData save;save.username = username;save.playTime = currentTime;save.totalPlayTime = elapsedTime;save.length = static_cast<int>(x.size());save.mode = currentMode;save.x = x;save.y = y;save.ax = ax;save.ay = ay;save.dir = dir;save.tk = tk;// 移除旧的存档for (auto it = saves.begin(); it != saves.end(); ) {if (it->username == username) {it = saves.erase(it);} else {++it;}}saves.push_back(save);ofstream file(SAVE_FILE, ios::binary);if (file) {const char* header = "SAV1";file.write(header, 4);size_t count = saves.size();file.write(reinterpret_cast<const char*>(&count), sizeof(count));for (const auto& s : saves) {size_t len = s.username.length();file.write(reinterpret_cast<const char*>(&len), sizeof(len));file.write(s.username.c_str(), len);file.write(reinterpret_cast<const char*>(&s.playTime), sizeof(s.playTime));file.write(reinterpret_cast<const char*>(&s.totalPlayTime), sizeof(s.totalPlayTime));file.write(reinterpret_cast<const char*>(&s.length), sizeof(s.length));file.write(reinterpret_cast<const char*>(&s.mode), sizeof(s.mode));len = s.x.size();file.write(reinterpret_cast<const char*>(&len), sizeof(len));for (int val : s.x) {file.write(reinterpret_cast<const char*>(&val), sizeof(val));}len = s.y.size();file.write(reinterpret_cast<const char*>(&len), sizeof(len));for (int val : s.y) {file.write(reinterpret_cast<const char*>(&val), sizeof(val));}file.write(reinterpret_cast<const char*>(&s.ax), sizeof(s.ax));file.write(reinterpret_cast<const char*>(&s.ay), sizeof(s.ay));file.write(reinterpret_cast<const char*>(&s.dir), sizeof(s.dir));file.write(reinterpret_cast<const char*>(&s.tk), sizeof(s.tk));}file.close();}}
}// 获取北京时间字符串
string getTimeStr(time_t t) {struct tm* tm_info;tm_info = localtime(&t);char buffer[26];strftime(buffer, 26, "%Y-%m-%d %H:%M:%S", tm_info);return string(buffer);
}
四、总结
该代码实现了一个功能丰富的登录 + 贪吃蛇游戏,包括用户注册、登录、游戏存档和加载、多种游戏模式等功能。通过合理的结构体定义和函数封装,代码具有较好的模块化和可维护性。同时,使用 Windows API 实现了窗口化的游戏界面和多媒体功能,提升了游戏的用户体验。不过,代码中保存游戏存档的部分还未完成将 saves
向量保存到文件的操作,需要进一步完善。