C++控制台狼人杀:面向对象设计、状态机与事件驱动编程实战

发布时间:2026/8/8 4:24:04
C++控制台狼人杀:面向对象设计、状态机与事件驱动编程实战 1. 项目概述为什么用C写控制台狼人杀最近在整理自己的项目库翻到了一个几年前用C写的控制台版狼人杀游戏。这个项目虽然界面简陋但麻雀虽小五脏俱全从游戏逻辑、角色交互到状态管理完整地走了一遍。很多朋友觉得C做游戏就得是那种带图形界面的“大工程”其实不然。用控制台命令行来实现狼人杀这类强逻辑、重交互的游戏恰恰是理解面向对象设计、状态机管理和事件驱动编程的绝佳练手项目。它剥离了复杂的图形渲染让你能专注于游戏核心机制的构建。如果你正在学习C想找一个比“学生管理系统”更有趣、比“贪吃蛇”更复杂的实战项目那从零开始搭一个狼人杀的控制台版本再合适不过了。这个项目适合有一定C基础至少熟悉类、继承、STL容器想挑战综合应用能力的朋友。整个过程你会涉及到如何用类来抽象游戏角色、如何设计游戏循环与阶段状态机、如何处理玩家输入与AI行为、如何管理游戏全局信息等等。最终你将得到一个能在命令行里运行支持人机对战或多人同机游玩的文字版狼人杀。下面我就把自己从零搭建这个项目的完整思路、关键代码和踩过的坑毫无保留地分享出来。2. 核心设计如何用面向对象思维建模狼人杀动手写代码之前最关键的一步是设计。狼人杀游戏包含多个角色狼人、村民、预言家、女巫等每个角色有独特的技能和胜利条件游戏按“夜晚-白天”的回合循环推进。我们需要用C的面向对象特性来优雅地描述这个世界。2.1 角色类的继承体系设计首先我们需要一个所有角色的基类。这个基类定义了角色的公共属性和行为接口。// Player.h - 玩家基类 #ifndef PLAYER_H #define PLAYER_H #include string #include vector class Player { protected: int id; // 玩家编号 std::string name; // 玩家名称 bool isAlive; // 是否存活 bool isSheriff; // 是否为警长 std::string roleName; // 角色名称如“狼人”、“村民” public: Player(int pid, const std::string pname); virtual ~Player() default; // 获取角色信息 int getId() const { return id; } std::string getName() const { return name; } bool getIsAlive() const { return isAlive; } std::string getRoleName() const { return roleName; } // 设置状态 void die() { isAlive false; } void setSheriff(bool status) { isSheriff status; } // 关键夜间行动纯虚函数由子类实现 virtual void nightAction(std::vectorPlayer* players) 0; // 关键白天投票 virtual void vote(std::vectorPlayer* players) 0; // 发言可为空实现 virtual void speak() {} }; #endif // PLAYER_H基类Player的核心是nightAction和vote这两个纯虚函数。这意味着任何具体的角色类如狼人、预言家都必须实现自己独特的夜间行动和投票逻辑。这是一种典型的“模板方法”模式的应用将公共流程固定在基类或游戏管理器中而将具体行为延迟到子类。接下来我们创建几个具体的角色类。这里以狼人 (Werewolf)和预言家 (Seer)为例// Werewolf.h #ifndef WEREWOLF_H #define WEREWOLF_H #include Player.h class Werewolf : public Player { public: Werewolf(int pid, const std::string pname); void nightAction(std::vectorPlayer* players) override; void vote(std::vectorPlayer* players) override; }; #endif // WEREWOLF_H// Werewolf.cpp #include Werewolf.h #include iostream #include cstdlib // for rand() Werewolf::Werewolf(int pid, const std::string pname) : Player(pid, pname) { roleName 狼人; isAlive true; } void Werewolf::nightAction(std::vectorPlayer* players) { if (!isAlive) return; // 死亡角色不行动 std::cout name 狼人正在选择击杀目标...\n; // 这里简化处理狼人AI随机选择一个存活的非狼人玩家 // 在实际项目中这里可以接入更复杂的AI逻辑或者等待真人玩家输入 std::vectorPlayer* potentialTargets; for (auto* p : players) { if (p-getIsAlive() p-getRoleName() ! 狼人) { potentialTargets.push_back(p); } } if (!potentialTargets.empty()) { int targetIndex rand() % potentialTargets.size(); Player* target potentialTargets[targetIndex]; std::cout 狼人阵营决定击杀 target-getName() std::endl; // 这里通常是将目标标记出来由游戏管理器统一处理死亡 // 例如GameManager::getInstance().setNightKillTarget(target); } } void Werewolf::vote(std::vectorPlayer* players) { // 狼人的投票逻辑白天公投 // 同样可以简化为随机投票或根据AI策略投票 std::cout name 狼人正在投票...\n; }预言家类的实现则展示了另一个方向的技能// Seer.cpp 中的 nightAction 实现 void Seer::nightAction(std::vectorPlayer* players) { if (!isAlive) return; std::cout name 预言家请选择一名玩家查验身份输入玩家编号:\n; // 显示存活玩家列表 for (auto* p : players) { if (p-getIsAlive()) { std::cout p-getId() . p-getName() \n; } } // 在实际控制台交互中这里应阻塞等待玩家输入 // 为简化示例我们假设预言家AI随机查验 std::vectorPlayer* alivePlayers; for (auto* p : players) { if (p-getIsAlive() p-getId() ! this-id) { // 不能查验自己 alivePlayers.push_back(p); } } if (!alivePlayers.empty()) { int checkIndex rand() % alivePlayers.size(); Player* checkedPlayer alivePlayers[checkIndex]; std::cout 预言家查验了 checkedPlayer-getName() 他的身份是 checkedPlayer-getRoleName() std::endl; // 查验结果通常只告知预言家本人这里我们输出到控制台模拟 } }设计心得使用继承体系的最大好处是扩展性。如果想增加一个新角色比如“猎人”你只需要创建一个新的Hunter类继承自Player并实现其特有的nightAction开枪技能和vote逻辑即可。游戏的主循环完全不需要修改因为它通过基类指针调用所有玩家的行动这就是多态的威力。但也要注意如果角色技能差异巨大纯虚函数接口可能会变得臃肿此时可以考虑使用“组件化”设计将技能拆分为独立的可组合对象。2.2 游戏状态机与回合管理狼人杀的游戏进程是高度结构化的遵循“夜晚→白天→投票→夜晚...”的循环。我们需要一个明确的状态机来管理这些阶段。我设计了一个GameState枚举和一个GameManager单例类来负责全局调度。// GameManager.h #ifndef GAME_MANAGER_H #define GAME_MANAGER_H #include vector #include memory #include Player.h enum class GameState { INIT, // 游戏初始化 NIGHT_START, // 夜幕降临 WEREWOLF_ACTION, // 狼人行动 SEER_ACTION, // 预言家行动 WITCH_ACTION, // 女巫行动 NIGHT_END, // 夜晚结束 DAYTIME, // 白天讨论 VOTING, // 投票放逐 DAY_END, // 白天结束 GAME_OVER // 游戏结束 }; class GameManager { private: static GameManager* instance; // 单例模式 GameState currentState; std::vectorstd::unique_ptrPlayer players; // 使用智能指针管理玩家对象 int dayCount; Player* nightKillTarget; // 夜晚被狼人击杀的目标 Player* votedPlayer; // 白天被投票放逐的目标 GameManager(); // 私有构造函数 public: GameManager(const GameManager) delete; GameManager operator(const GameManager) delete; static GameManager* getInstance(); // 游戏流程控制 void initializeGame(); // 初始化游戏分配角色 void runGameLoop(); // 主游戏循环 void proceedToNextState(); // 状态转移 // 状态查询与设置 GameState getCurrentState() const { return currentState; } void setCurrentState(GameState state) { currentState state; } // 玩家管理 void addPlayer(std::unique_ptrPlayer player); const std::vectorstd::unique_ptrPlayer getPlayers() const { return players; } // 行动结果设置 void setNightKillTarget(Player* target) { nightKillTarget target; } void setVotedPlayer(Player* target) { votedPlayer target; } // 游戏逻辑判断 bool checkGameOver(); // 检查游戏是否结束狼人或好人阵营胜利 void announceResult(); // 宣布游戏结果 }; #endif // GAME_MANAGER_HGameManager是整个游戏的大脑。它通过runGameLoop函数驱动状态机运转// GameManager.cpp 中的 runGameLoop 函数核心部分 void GameManager::runGameLoop() { currentState GameState::INIT; initializeGame(); while (currentState ! GameState::GAME_OVER) { switch (currentState) { case GameState::NIGHT_START: std::cout \n 第 dayCount 天夜晚 \n; std::cout 夜幕降临所有玩家请闭眼。\n; proceedToNextState(); break; case GameState::WEREWOLF_ACTION: std::cout \n【狼人请睁眼】\n; for (auto player : players) { if (player-getIsAlive() player-getRoleName() 狼人) { player-nightAction(players); // 多态调用狼人行动 } } // 此处应收集狼人的击杀目标并暂存到 nightKillTarget proceedToNextState(); break; case GameState::SEER_ACTION: std::cout \n【预言家请睁眼】\n; for (auto player : players) { if (player-getIsAlive() player-getRoleName() 预言家) { player-nightAction(players); // 多态调用预言家行动 } } proceedToNextState(); break; case GameState::DAYTIME: std::cout \n 第 dayCount 天白天 \n; std::cout 天亮了大家请睁眼。\n; // 宣布昨夜死亡信息如果有 if (nightKillTarget) { std::cout 昨晚 nightKillTarget-getName() 不幸遇害。\n; nightKillTarget-die(); nightKillTarget nullptr; } else { std::cout 昨晚是个平安夜。\n; } // 进入发言环节简化版可轮流发言 for (auto player : players) { if (player-getIsAlive()) { player-speak(); } } proceedToNextState(); break; case GameState::VOTING: std::cout \n【开始投票】\n; // 所有存活玩家进行投票 // 简化这里可以调用每个玩家的 vote 方法并统计票数 // 实际应实现更复杂的投票逻辑包括平票处理等 // ... // 假设投票结果已产生并设置 votedPlayer if (votedPlayer) { std::cout votedPlayer-getName() 被投票放逐。\n; votedPlayer-die(); votedPlayer nullptr; } proceedToNextState(); break; // ... 其他状态处理 default: proceedToNextState(); } // 每次状态转换后检查游戏是否结束 if (checkGameOver()) { currentState GameState::GAME_OVER; announceResult(); } } }状态机设计要点这里我使用了最简单的switch-case来实现状态机对于狼人杀这种线性流程清晰的项目是够用的。proceedToNextState()函数负责根据当前状态决定下一个状态是什么例如WEREWOLF_ACTION之后是SEER_ACTION。这种设计让游戏流程一目了然调试起来也非常方便。如果你想支持更复杂的状态跳转比如女巫救人或毒人后可能跳过某些阶段可以在proceedToNextState中加入条件判断。3. 关键实现控制台交互、AI与数据管理有了骨架我们需要填充血肉。控制台游戏的核心交互就是输入输出而为了让游戏能“自己玩起来”我们还需要为角色注入简单的AI。3.1 控制台输入输出与界面美化纯文本的控制台也可以做得友好。我们需要一个统一的InputHelper和OutputHelper来管理交互。// IOHelper.h #ifndef IOHELPER_H #define IOHELPER_H #include string #include vector #include limits class IOHelper { public: // 获取一个范围内的整数输入 static int getIntInput(int min, int max, const std::string prompt ) { int input; while (true) { if (!prompt.empty()) { std::cout prompt; } std::cin input; if (std::cin.fail() || input min || input max) { std::cin.clear(); // 清除错误状态 std::cin.ignore(std::numeric_limitsstd::streamsize::max(), \n); // 忽略错误输入 std::cout 输入无效请输入一个 min 到 max 之间的整数。\n; } else { std::cin.ignore(std::numeric_limitsstd::streamsize::max(), \n); // 清空输入缓冲区 return input; } } } // 获取字符串输入 static std::string getStringInput(const std::string prompt ) { std::string input; if (!prompt.empty()) { std::cout prompt; } std::getline(std::cin, input); return input; } // 清屏跨平台简化版 static void clearScreen() { #ifdef _WIN32 system(cls); #else system(clear); #endif } // 打印分隔线 static void printSeparator(char ch , int length 50) { std::cout std::string(length, ch) std::endl; } // 打印带颜色的文本Windows控制台示例Linux/macOS需用ANSI转义码 static void printColored(const std::string text, int colorCode) { #ifdef _WIN32 HANDLE hConsole GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hConsole, colorCode); std::cout text; SetConsoleTextAttribute(hConsole, 7); // 恢复默认白色 #else // ANSI 转义序列例如\033[31m 红色 std::cout \033[ colorCode m text \033[0m; #endif } }; #endif // IOHELPER_H在游戏初始化时我们可以用这些辅助函数来创建玩家void GameManager::initializeGame() { IOHelper::clearScreen(); std::cout 狼人杀游戏初始化 \n; int playerCount IOHelper::getIntInput(6, 12, 请输入玩家人数 (6-12): ); // 预定义角色配置简化版3狼、1预言家、1女巫、其余村民 std::vectorstd::string rolePool; rolePool.insert(rolePool.end(), 3, 狼人); rolePool.push_back(预言家); rolePool.push_back(女巫); // 填充村民 for (int i rolePool.size(); i playerCount; i) { rolePool.push_back(村民); } // 随机打乱角色池 std::random_shuffle(rolePool.begin(), rolePool.end()); for (int i 0; i playerCount; i) { std::string playerName 玩家 std::to_string(i 1); std::string role rolePool[i]; std::unique_ptrPlayer player; if (role 狼人) { player std::make_uniqueWerewolf(i, playerName); } else if (role 预言家) { player std::make_uniqueSeer(i, playerName); } else if (role 女巫) { // player std::make_uniqueWitch(i, playerName); player std::make_uniquePlayer(i, playerName); // 先用基类占位 player-setRoleName(女巫); } else { // 村民或其他角色 player std::make_uniquePlayer(i, playerName); player-setRoleName(role); } addPlayer(std::move(player)); std::cout playerName 的身份是【 role 】\n; } std::cout \n角色分配完毕游戏即将开始\n; IOHelper::getStringInput(按回车键继续...); currentState GameState::NIGHT_START; }交互设计避坑控制台输入最怕的就是缓冲区残留和类型错误。IOHelper::getIntInput函数中的std::cin.clear()和std::cin.ignore(...)是黄金搭档能有效处理用户输入非数字或超出范围的情况避免程序陷入死循环。另外在每次std::cin 后使用ignore清空缓冲区可以防止后续的getline读到残留的回车符。3.2 简易AI逻辑的实现为了让游戏能在单人模式下运行或者填充空缺玩家我们需要为角色实现基本的AI。AI的复杂度可以天差地别这里我们实现一个“随机但合理”的初级AI。以狼人AI为例进阶一点可以不只是随机杀人// 在 Werewolf.cpp 中实现一个稍智能的 nightAction void Werewolf::nightAction(std::vectorPlayer* allPlayers) { if (!isAlive) return; // 1. 找出所有狼人队友 std::vectorPlayer* werewolfTeam; // 2. 找出所有存活的好人目标 std::vectorPlayer* goodTargets; for (auto* p : allPlayers) { if (!p-getIsAlive()) continue; if (p-getRoleName() 狼人) { werewolfTeam.push_back(p); } else { goodTargets.push_back(p); } } // 如果没有好人可杀则跳过 if (goodTargets.empty()) { std::cout name 狼人找不到可击杀的目标。\n; return; } // 简易策略优先击杀未被怀疑的玩家这里用随机模拟 // 在实际中可以维护一个“怀疑度”表AI狼人会选择怀疑度最低的玩家下手以隐藏自己。 int targetIndex rand() % goodTargets.size(); Player* chosenTarget goodTargets[targetIndex]; std::cout 狼人阵营经过讨论决定击杀 chosenTarget-getName() std::endl; // 通知游戏管理器 GameManager::getInstance()-setNightKillTarget(chosenTarget); }对于预言家AI可以设计为优先查验发言可疑的玩家或者随机查验。我们可以为Player基类增加一个suspicionLevel怀疑度属性AI在行动时可以参考这个值。// 在Player基类中添加 class Player { // ... 其他成员 private: int suspicionLevel; // 怀疑度越高越可疑 public: int getSuspicionLevel() const { return suspicionLevel; } void increaseSuspicion(int amount 1) { suspicionLevel amount; } void decreaseSuspicion(int amount 1) { suspicionLevel std::max(0, suspicionLevel - amount); } }; // 在游戏发言或投票环节可以动态调整玩家的怀疑度AI设计心得游戏AI是一个无底洞从完全随机到深度学习都可以。对于练手项目建议采用“规则随机”的混合策略。例如狼人AI可以设定几条简单规则1) 不杀队友2) 优先杀神职如果能判断的话3) 其次杀发言少的“潜水”玩家。用权重和随机数来模拟决策过程既简单又能体现一定的策略性代码也不会太复杂。3.3 游戏数据持久化与回放一个增强功能点是游戏记录。我们可以设计一个GameLogger单例类负责将重要的游戏事件角色分配、夜间行动、死亡、投票结果记录到内存或文件中。// GameLogger.h #ifndef GAME_LOGGER_H #define GAME_LOGGER_H #include string #include vector #include fstream #include chrono struct GameEvent { std::string timestamp; std::string description; GameEvent(const std::string desc); }; class GameLogger { private: static GameLogger* instance; std::vectorGameEvent eventLog; std::ofstream logFile; bool fileLoggingEnabled; GameLogger(); public: static GameLogger* getInstance(); ~GameLogger(); void logEvent(const std::string eventDesc); void printLog() const; void saveLogToFile(const std::string filename); }; #endif // GAME_LOGGER_H在GameManager的每个关键节点调用GameLogger::getInstance()-logEvent(天亮了)。这样游戏结束后不仅可以打印文字战报还能将日志保存为文件甚至可以实现“游戏回放”功能——通过解析日志文件一步步重现游戏过程。这对于调试和复盘游戏逻辑非常有帮助。4. 进阶优化让游戏更健壮、更可玩基础版本跑通后我们可以从性能、可玩性和代码质量三个方面进行优化。4.1 使用智能指针管理资源避免内存泄漏注意看之前的GameManager我使用了std::vectorstd::unique_ptrPlayer players来存储玩家。std::unique_ptr是C11引入的智能指针它独占对象的所有权。当GameManager析构或者players向量被清空时所有Player对象都会被自动删除无需手动delete。这是现代C避免内存泄漏的最佳实践之一。如果游戏中存在共享的对象比如一个全局的“技能效果”库可以考虑使用std::shared_ptr。但在狼人杀游戏里每个玩家对象生命周期明确归属于游戏管理器用unique_ptr更合适也更高效。4.2 引入简单的事件系统解耦模块随着角色和技能增多游戏逻辑会越来越复杂。比如女巫救人的动作需要知道狼人击杀了谁猎人死亡时需要触发开枪技能。如果这些逻辑全部硬编码在GameManager的switch-case里代码会变得难以维护。我们可以引入一个简单的事件Event系统// Event.h enum class EventType { PLAYER_KILLED, PLAYER_VOTED_OUT, WITCH_SAVED, // ... 其他事件 }; struct GameEvent { EventType type; Player* sourcePlayer; // 事件源 Player* targetPlayer; // 事件目标 // ... 其他事件数据 }; // 任何模块如Player子类、GameManager都可以发布事件 // 一个全局的事件处理器或称为事件总线负责接收事件并通知所有监听者 class EventDispatcher { std::vectorstd::functionvoid(const GameEvent) listeners; public: void addListener(std::functionvoid(const GameEvent) listener) { listeners.push_back(listener); } void dispatchEvent(const GameEvent event) { for (auto listener : listeners) { listener(event); } } };然后猎人角色可以监听PLAYER_KILLED事件并且检查事件中的targetPlayer是不是自己。如果是则触发开枪技能。这样GameManager就不需要知道猎人技能的具体实现只需要在玩家死亡时发布一个PLAYER_KILLED事件即可实现了模块间的解耦。4.3 配置化与数据驱动硬编码角色配置3狼、1预言家...不够灵活。我们可以将游戏配置玩家人数、角色种类及数量放到一个外部文件如JSON中。// game_config.json { playerCount: 8, roles: [ { name: 狼人, count: 2 }, { name: 预言家, count: 1 }, { name: 女巫, count: 1 }, { name: 猎人, count: 1 }, { name: 村民, count: 3 } ] }在GameManager::initializeGame()中读取这个JSON文件来创建角色池。这样想要调整游戏配置或添加新角色如“丘比特”、“白痴”只需要修改配置文件而无需重新编译代码。你可以使用像 nlohmann/json 这样易用的C JSON库来解析配置文件。5. 常见问题与调试技巧实录在开发过程中我遇到了不少典型问题这里记录下排查思路和解决方法。5.1 多态调用不生效虚函数表问题问题现象我通过基类Player*指针调用nightAction()但执行的都是基类的空实现而不是子类如Werewolf的重写版本。排查过程首先检查子类声明确保Werewolf类用public继承自Player。检查函数签名确保子类中nightAction的函数签名返回类型、参数列表、const限定与基类中的virtual函数完全一致。一个常见的错误是在子类中漏写了override关键字C11虽然不写也能编译但写了可以让编译器帮你检查签名是否正确。检查对象的创建方式这是最容易被忽略的一点。错误示例std::vectorPlayer players; // 存储的是对象会发生“对象切片” players.push_back(Werewolf(1, 狼人1)); // Werewolf对象被强制转换为Player对象子类特有部分被“切掉” players[0].nightAction(...); // 调用的是Player::nightAction不是Werewolf的正确做法 必须使用指针或引用来实现多态。我选择了存储智能指针std::vectorstd::unique_ptrPlayer players; players.push_back(std::make_uniqueWerewolf(1, 狼人1)); players[0]-nightAction(...); // 正确调用Werewolf::nightAction5.2 游戏状态混乱或卡死问题现象游戏流程没有按预想的“夜晚-白天”顺序进行或者卡在某个状态无法前进。排查与解决打印状态日志在GameManager::proceedToNextState()函数和每个case的开头打印当前状态和进入/退出信息。这是最直接的调试方法。void GameManager::proceedToNextState() { std::cout [状态转移] 从 static_castint(currentState) 转移到 ; // ... 计算 nextState ... std::cout static_castint(nextState) std::endl; currentState nextState; }检查状态转移条件确保proceedToNextState中的逻辑覆盖了所有可能的分支。特别是像女巫是否用药、是否有人死亡等条件可能会跳过某些阶段。使用断言在关键位置使用assert例如assert(currentState GameState::INIT currentState GameState::GAME_OVER)可以在调试版本中快速捕获非法状态。绘制状态转移图在纸上或使用绘图工具画出你设计的状态机对照代码检查能直观地发现逻辑漏洞。5.3 输入处理导致无限循环问题现象当要求玩家输入数字选择目标时如果输入了字母程序陷入疯狂打印错误信息的循环。原因与解决这就是前面提到的输入缓冲区问题。当std::cin intVar遇到非数字输入时它会设置失败标志并且不会从缓冲区提取非法字符。这个非法字符会一直留在缓冲区导致下一次std::cin 继续失败形成死循环。标准解决方案已在IOHelper::getIntInput中体现if (std::cin.fail()) { std::cin.clear(); // 1. 清除错误状态标志 std::cin.ignore(std::numeric_limitsstd::streamsize::max(), \n); // 2. 清空缓冲区直到换行符 // 然后提示用户重新输入 }std::numeric_limitsstd::streamsize::max()表示一个非常大的数字确保清空所有残留字符。5.4 随机数总是相同问题现象每次运行游戏狼人AI总是击杀同一个目标预言家总是查验同一个人缺乏随机性。原因C/C 的rand()函数生成的是伪随机数它的序列由种子seed决定。如果种子不变默认通常是1每次程序启动生成的随机数序列就完全相同。解决在程序开始时通常在main()函数里用当前时间设置随机数种子。#include cstdlib #include ctime int main() { std::srand(static_castunsigned int(std::time(nullptr))); // 用时间做种子 // ... 游戏启动逻辑 return 0; }对于更高质量的随机数需求如游戏平衡性要求高可以考虑使用 C11 的random库它提供了更多分布类型如均匀分布、正态分布和更可靠的随机数引擎。5.5 游戏平衡性测试项目基本完成后如何测试游戏是否平衡比如好人胜率是否不至于太低简易测试方法写一个自动化测试脚本可以就在main函数里屏蔽所有控制台输出让游戏自动运行成千上万次。void runSimulation(int times) { int goodWins 0; int werewolfWins 0; for (int i 0; i times; i) { // 1. 重定向cout到空流屏蔽输出可选 // std::cout.setstate(std::ios_base::failbit); // 或者使用一个空的stringstream: std::stringstream nullStream; std::cout.rdbuf(nullStream.rdbuf()); // 2. 创建并运行一局游戏 GameManager* gm GameManager::getInstance(); gm-initializeGame(); gm-runGameLoop(); // 需要修改runGameLoop使其不依赖交互完全由AI驱动 // 3. 获取并统计结果 if (gm-getWinner() 好人阵营) goodWins; else werewolfWins; // 4. 重置GameManager单例准备下一局需要实现reset函数 gm-reset(); } // 5. 打印统计结果 std::cout 模拟 times 局结果\n; std::cout 好人阵营胜利: goodWins 局 ( (goodWins*100.0/times) %)\n; std::cout 狼人阵营胜利: werewolfWins 局 ( (werewolfWins*100.0/times) %)\n; }通过大量模拟你可以发现当前的角色配置和AI逻辑下哪一方优势过大从而调整角色数量或AI策略使游戏更平衡。从零开始用C开发一个控制台狼人杀远不止是语法练习。它强迫你思考如何将复杂的现实规则抽象为类和对象如何设计清晰的状态流转如何处理用户输入和异常以及如何让代码易于扩展和维护。过程中遇到的每一个坑从虚函数表到输入缓冲区从内存管理到模块解耦都是成长为一名合格C开发者的宝贵经验。这个项目代码量不大但涵盖的知识点很全做完之后你对C面向对象、标准库的使用以及程序设计的整体把控能力肯定会上一个台阶。