// ============================================================
// 恶魔轮盘 (Buckshot Roulette) —— 控制台文字版
// 参考: https://baike.baidu.com/item/恶魔轮盘/64275291
//
// * 特性
// 1. 核心玩法: 玩家 vs 恶魔轮流用霰弹枪射击(实弹/空弹), 配合道具博弈
// 2. 道具系统: 放大镜 / 小刀 / 手铐 / 啤酒 / 香烟 / 肾上腺素 / 逆转器 / 电话 / 过期药品
// 3. 多种模式: 经典 / 开局供给道具 / 无道具 / 无限道具
// 4. 三种难度: 简单(恶魔随机) / 中等(恶魔概率分析) / 困难(恶魔透视弹膛)
// 5. 多存档: 局中可保存到 1~9 号存档槽, 之后读取继续
// 6. 分数系统: 对局结束自动计分, 分数排行榜可浏览
// 7. 回放系统: 每局自动保存, 可随时选择回放
// 8. 上帝模式: 对局结束后可复盘, 查看枪膛内子弹排列等真实信息
//
// * 编译环境
// 适用于 Dev-C++ 6.5 (MinGW), 纯 C++98 风格, 不依赖 C++11 特性
// 【重要】编码说明:
// 1) 本文件使用 UTF-8 编码保存 —— Red Panda Dev-C++ 6.5 自带 GCC10,
// 默认按 UTF-8 读取源码, 若存成 ANSI/GBK 会报 "failure to convert gbk to UTF-8"。
// 2) 编译时请在 Red Panda 的【工具->编译器选项】里添加参数:
// -fexec-charset=GBK
// 作用: 让可执行文件内的中文字符串以 GBK 存储, 与 Windows 控制台的
// GBK 显示一致, 中文即可正常显示(不设置的话运行时中文会乱码)。
// ============================================================
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#include <direct.h>
#else
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <termios.h>
#endif
// ---------- 常量 ----------
#define ITEM_MAG 0 // 放大镜
#define ITEM_KNIFE 1 // 小刀
#define ITEM_CUFF 2 // 手铐
#define ITEM_BEER 3 // 啤酒
#define ITEM_CIG 4 // 香烟
#define ITEM_ADREN 5 // 肾上腺素
#define ITEM_INVERT 6 // 逆转器
#define ITEM_PHONE 7 // 电话
#define ITEM_PILL 8 // 过期药品
#define NITEM 9
#define MAXSHELL 16
#define MAXLOG 2000
#define MAXSLOT 9
// 模式
#define MODE_CLASSIC 1 // 经典: 每轮随机发2个道具
#define MODE_HEADSTART 2 // 开局供给: 第1轮每人4个道具, 之后每轮2个
#define MODE_NOITEM 3 // 无道具: 纯对拼
#define MODE_INFINITE 4 // 无限道具: 每轮补满到8个
// 难度
#define DIFF_EASY 0 // 简单: 恶魔随机开枪
#define DIFF_NORMAL 1 // 中等: 恶魔做概率分析选择
#define DIFF_HARD 2 // 困难: 恶魔直接透视枪膛内子弹
// 测试开关(仅命令行参数 autotest/flowtest 时启用, 正常游玩不受影响)
int g_auto = 0; // 自动玩家: 玩家回合自动射击对方
int g_noPause = 0; // 跳过"按任意键继续"等待
// 全局设置
int g_showShell = 1; // 设置: 对局中是否实时显示本轮实弹/空弹总数(默认开)
int g_showRemain = 1; // 设置: 辅助计算并显示剩余实弹/空弹数量(默认开)
int g_autoCertain = 1; // 设置: 全空弹/全实弹确定局面自动执行(默认开)
int g_resume = 0; // 读取存档后继续: 不重置当前大轮生命
static char g_lastMsg[96]; // 道具/射击后的即时反馈消息
// 行动者(日志)
#define ACT_PLAYER 0
#define ACT_DEALER 1
#define ACT_SYS 2
// ---------- 数据结构 ----------
struct LogEntry {
int round; // 装填轮编号
int actor; // 行动者
char act[16]; // 动作名
char detail[96]; // 结果描述
int isLive; // 开枪时该发是否实弹, -1 表示非开枪
int dmg; // 造成伤害
};
struct Game {
char magic[8]; // 存档标识 "BRSAVE5"
int version;
char playerName[24];
char matchTime[24]; // 对局时间
int mode; // 模式
int difficulty; // 难度
int roundNum; // 当前装填轮(从1开始)
int hp[2]; // 0玩家 1恶魔
int maxHp[2];
int shells[MAXSHELL]; // 弹膛排列 1实弹 0空弹
int nShells;
int pos; // 当前枪膛指针(下一发下标)
int itemCnt[2][NITEM]; // 道具计数
int knifeReady; // 刀: 下一发伤害=2
int cuffed[2]; // 手铐标记
int playerTurn; // 1玩家回合 0恶魔回合
int revealNext; // 放大镜揭示 -1未看 0空弹 1实弹
int phoneN; // 电话提示条数(支持连续使用多个手机全部保留)
int phonePosArr[MAXSHELL]; // 电话预测的子弹位置
int phoneValArr[MAXSHELL]; // 电话预测值 0空/1实
int godMode; // 上帝模式开关(赛后/回放可用)
int blanksSelfHit; // 空弹打自己次数(计分)
int over; // 0进行中 1结束 2放弃
int winner; // -1未定 0玩家 1恶魔
int score;
int stage; // 当前大轮 1..3 (一局固定3轮)
int stageWins[2]; // 双方赢得的轮数 0玩家 1恶魔
LogEntry log[MAXLOG];
int logLen;
int liveCnt; // 本轮实弹数(装弹时告知玩家)
int blankCnt; // 本轮空弹数
};
// ---------- 工具函数 ----------
static int my_getch(); // 提前声明, 供 pause1 使用
static void setColor(int c) {
#ifdef _WIN32
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(h, (WORD)c);
#else
(void)c;
#endif
}
// 颜色: 7白 12亮红 10亮绿 14黄 11亮青 13亮紫 9亮蓝 15亮白 4红 6棕 8灰
static void cls() {
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
static void pause1() {
if (g_noPause) return;
printf("\n 按任意键继续...");
fflush(stdout);
#ifdef _WIN32
_getch();
#else
my_getch();
#endif
printf("\n");
}
// 读取多位数(数字键输入并回显, 回车结束; 返回-1表示无输入)
static int readNum() {
int val = 0, got = 0;
while (1) {
int ch = my_getch();
if (ch == '\r' || ch == '\n') break;
if (ch >= '0' && ch <= '9') {
val = val * 10 + (ch - '0');
got = 1;
printf("%c", ch);
fflush(stdout);
} else break;
}
printf("\n");
return got ? val : -1;
}
// 显示最近一次操作(射击/道具)的即时反馈
static void printLastMsg(const char* who) {
if (g_auto) return;
if (g_lastMsg[0]) {
setColor(14);
printf(" >> %s: %s\n", who, g_lastMsg);
setColor(7);
pause1();
}
}
// 统一无回显按键读取
static int my_getch() {
#ifdef _WIN32
return _getch();
#else
struct termios oldt, newt;
int ch;
tcgetattr(0, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(0, TCSANOW, &newt);
ch = getchar();
tcsetattr(0, TCSANOW, &oldt);
if (ch == EOF) exit(0); // 输入流关闭(如管道/重定向), 安全退出, 避免死循环
return ch;
#endif
}
static void shuffleArr(int* a, int n) { // Fisher-Yates
int i;
for (i = n - 1; i > 0; i--) {
int j = rand() % (i + 1);
int t = a[i]; a[i] = a[j]; a[j] = t;
}
}
static void nowStr(char* buf, int cap) { // 当前时间字符串
time_t t = time(NULL);
struct tm* tm = localtime(&t);
if (tm == NULL) { strcpy(buf, "unknown"); return; }
strftime(buf, cap, "%Y-%m-%d %H:%M:%S", tm);
}
static void ensureDirs() {
#ifdef _WIN32
_mkdir("matches");
#else
mkdir("matches", 0755);
#endif
}
static const char* modeName(int m) {
switch (m) {
case MODE_CLASSIC: return "经典";
case MODE_HEADSTART: return "开局供给";
case MODE_NOITEM: return "无道具";
case MODE_INFINITE: return "无限道具";
}
return "未知";
}
static const char* diffName(int d) {
switch (d) {
case DIFF_EASY: return "简单";
case DIFF_NORMAL: return "中等";
case DIFF_HARD: return "困难";
}
return "未知";
}
static const char* itemName(int it) {
switch (it) {
case ITEM_MAG: return "放大镜";
case ITEM_KNIFE: return "小刀";
case ITEM_CUFF: return "手铐";
case ITEM_BEER: return "啤酒";
case ITEM_CIG: return "香烟";
case ITEM_ADREN: return "肾上腺素";
case ITEM_INVERT: return "逆转器";
case ITEM_PHONE: return "电话";
case ITEM_PILL: return "过期药品";
}
return "?";
}
static const char* actorName(int a) {
if (a == ACT_PLAYER) return "玩家";
if (a == ACT_DEALER) return "恶魔";
return "系统";
}
// ============================================================
// 以下为游戏核心逻辑(函数实现)
// ============================================================
void resetGame(Game& g, int mode, int diff, const char* name);
void roundConfig(int rn, int& live, int& blank);
void newRound(Game& g);
void giveItems(Game& g, int who);
void addLog(Game& g, int actor, const char* act, const char* detail, int isLive, int dmg);
int calcPLive(Game& g);
int fire(Game& g, int target); // target: 0自己 1对方
int applyItem(Game& g, int who, int it); // 返回 0不耗行动权 1回合结束 2无效
void printStatus(const Game& g);
void printShellGod(const Game& g);
void dealerAction(Game& g);
int playerAction(Game& g);
void runMatch(Game& g);
void computeScore(Game& g);
void printResult(const Game& g);
void godReview(const Game& g);
// 存档/回放/分数
int saveSlot(Game& g, int slot);
int loadSlot(Game& g, int slot);
void saveMatch(Game& g);
void addScoreRecord(const Game& g);
void showScores();
void replayMatch(const char* file);
// 菜单
void help();
int modeSelect();
int diffSelect();
int mainMenu();
void runAutoTest();
// ---------- 游戏基础 ----------
void resetGame(Game& g, int mode, int diff, const char* name) {
memset(&g, 0, sizeof(Game));
strcpy(g.magic, "BRSAVE5");
g.version = 1;
strncpy(g.playerName, name, 23); g.playerName[23] = 0;
nowStr(g.matchTime, sizeof(g.matchTime));
g.mode = mode;
g.difficulty = diff;
g.stage = 1;
g.stageWins[0] = 0;
g.stageWins[1] = 0;
g.maxHp[0] = 4; // 所有难度双方血量相等, 玩家都是4血
g.maxHp[1] = 4;
g.hp[0] = g.maxHp[0];
g.hp[1] = g.maxHp[1];
g.playerTurn = 1; // 玩家先手
g.revealNext = -1;
g.phoneN = 0;
g.winner = -1;
g.over = 0;
}
void roundConfig(int rn, int& live, int& blank) {
// 每大轮子弹数随机(数量不一定): 第1轮 2~4 发, 第2轮 3~5 发, 第3轮 4~6 发
// 但实弹与空弹各至少1发(不能全部是实弹或全部是空弹)
int total;
if (rn <= 1) total = 2 + rand() % 3; // 2~4
else if (rn == 2) total = 3 + rand() % 3; // 3~5
else total = 4 + rand() % 3; // 4~6
if (total > MAXSHELL - 2) total = MAXSHELL - 2;
if (total < 2) total = 2;
live = 1 + rand() % (total - 1); // 实弹 1..total-1
blank = total - live; // 空弹 至少1发
}
void addLog(Game& g, int actor, const char* act, const char* detail, int isLive, int dmg) {
if (g.logLen >= MAXLOG) return;
LogEntry& L = g.log[g.logLen++];
L.round = g.roundNum;
L.actor = actor;
L.isLive = isLive;
L.dmg = dmg;
strncpy(L.act, act, 15); L.act[15] = 0;
strncpy(L.detail, detail, 95); L.detail[95] = 0;
}
void giveItems(Game& g, int who) {
if (g.mode == MODE_NOITEM) return;
int give = 2;
if (g.mode == MODE_HEADSTART && g.roundNum == 1) give = 4;
if (g.mode == MODE_INFINITE) give = 8;
int i, total = 0, k;
for (k = 0; k < NITEM; k++) total += g.itemCnt[who][k];
for (i = 0; i < give; i++) {
if (total >= 8) break;
int it = rand() % NITEM;
g.itemCnt[who][it]++;
total++;
}
}
void newRound(Game& g) {
g.roundNum++;
int live, blank, i;
roundConfig(g.stage, live, blank); // 按大轮设定子弹数量范围
g.nShells = live + blank;
g.liveCnt = live; // 记录本轮实弹数(玩家可见)
g.blankCnt = blank; // 记录本轮空弹数
for (i = 0; i < live; i++) g.shells[i] = 1;
for (; i < g.nShells; i++) g.shells[i] = 0;
shuffleArr(g.shells, g.nShells);
g.pos = 0;
g.revealNext = -1;
g.phoneN = 0; // 换弹膛后旧电话预测作废
g.knifeReady = 0; // 换弹膛, 小刀的"下一发"重置(小刀道具保留)
giveItems(g, 0);
giveItems(g, 1);
char det[96];
sprintf(det, "装填完成: %d实弹 %d空弹 (共%d发)", live, blank, g.nShells);
addLog(g, ACT_SYS, "开始新轮", det, -1, 0);
}
int calcPLive(Game& g) { // 当前下一发为实弹的概率(0-100)
int rem = g.nShells - g.pos, live = 0, i;
if (rem <= 0) return 0;
for (i = g.pos; i < g.nShells; i++) if (g.shells[i]) live++;
return live * 100 / rem;
}
// 电话提示失效处理: 移除预测位置==idx 的所有条目(该发已被发射/弹出)
static void phoneRemove(Game& g, int idx) {
int i;
for (i = 0; i < g.phoneN; i++) {
if (g.phonePosArr[i] == idx) {
int j;
for (j = i; j < g.phoneN - 1; j++) {
g.phonePosArr[j] = g.phonePosArr[j + 1];
g.phoneValArr[j] = g.phoneValArr[j + 1];
}
g.phoneN--;
i--;
}
}
}
// target: 0=射击自己 1=射击对方; 返回造成的伤害
int fire(Game& g, int target) {
int actor = g.playerTurn ? ACT_PLAYER : ACT_DEALER;
int vic = (target == 1) ? (1 - actor) : actor; // 受害者
int firedPos = g.pos; // 记录发射前位置(电话判断用)
int isLive = g.shells[g.pos];
g.pos++;
int dmg;
if (g.knifeReady) { g.knifeReady = 0; dmg = isLive ? 2 : 0; } // 小刀: 仅实弹翻倍, 空弹仍0
else dmg = isLive ? 1 : 0;
if (g.hp[vic] > 0) g.hp[vic] -= dmg;
char det[96];
if (dmg == 0) sprintf(det, "空弹! 没有造成伤害");
else if (isLive) sprintf(det, "实弹命中! 造成%d点伤害", dmg);
else sprintf(det, "命中! 造成%d点伤害", dmg);
addLog(g, actor, (target == 1) ? "射击对方" : "射击自己", det, isLive, dmg);
// 即时反馈: 明确是谁、向谁射击、结果如何
{
const char* shooter = (actor == ACT_PLAYER) ? "你" : "恶魔";
const char* aim = (target == 1) ? ((actor == ACT_PLAYER) ? "恶魔" : "你") : "自己";
if (dmg == 0) {
if (target == 1) sprintf(g_lastMsg, "%s射击%s: 空弹! 白送回合", shooter, aim);
else sprintf(g_lastMsg, "%s射击自己: 空弹! 无伤保留回合", shooter);
} else {
sprintf(g_lastMsg, "%s射击%s: 实弹命中! 造成%d点伤害", shooter, aim, dmg);
}
}
if (target == 1) {
// 打对方: 无论实空, 行动权交给对方
g.playerTurn = !g.playerTurn;
} else {
// 打自己
if (dmg > 0) {
g.playerTurn = !g.playerTurn; // 受伤 -> 行动权转移
} else {
g.blanksSelfHit++; // 空弹保回合
}
}
g.revealNext = -1;
// 电话: 预测的那一发被真正发射后才消失; 其他位置的预测信息全部保留
if (g.phoneN > 0) phoneRemove(g, firedPos);
return dmg;
}
// 使用道具; 返回 0=不耗行动权(可继续) 1=回合结束 2=无效
int applyItem(Game& g, int who, int it) {
int op = 1 - who;
int* cnt = g.itemCnt[who];
if (cnt[it] <= 0) return 2;
cnt[it]--;
char det[96];
switch (it) {
case ITEM_MAG:
if (g.pos < g.nShells) g.revealNext = g.shells[g.pos];
else g.revealNext = -1;
if (g.revealNext == 1) sprintf(det, "[放大镜] 看到下一发: 实弹!");
else if (g.revealNext == 0) sprintf(det, "[放大镜] 看到下一发: 空弹!");
else sprintf(det, "枪膛已空");
strcpy(g_lastMsg, det);
addLog(g, who, "放大镜", det, g.revealNext, 0);
return 0;
case ITEM_KNIFE:
g.knifeReady = 1;
strcpy(g_lastMsg, "[刀] 利刃出鞘: 下一发伤害翻倍(2点)");
addLog(g, who, "刀", "利刃出鞘: 下一发伤害翻倍(2点)", -1, 0);
return 0;
case ITEM_CUFF:
g.cuffed[op] = 1;
sprintf(det, "[手铐] 铐住%s: 跳过其下一个回合", (op == ACT_PLAYER) ? "玩家" : "恶魔");
strcpy(g_lastMsg, det);
addLog(g, who, "手铐", det, -1, 0);
return 0;
case ITEM_BEER:
if (g.pos < g.nShells) {
int was = g.shells[g.pos];
int idx = g.pos; // 被弹出的那发(第 idx+1 发)
g.pos++;
if (g.phoneN > 0) phoneRemove(g, idx); // 被弹出的正是电话预测的那发, 该提示作废
sprintf(det, "[啤酒] 弹出了第%d发: %s! (剩余%d发)", idx + 1, was ? "实弹" : "空弹", g.nShells - g.pos);
} else {
sprintf(det, "[啤酒] 枪膛已空");
}
g.revealNext = -1; // 弹出后下一发已变化, 放大镜信息作废
strcpy(g_lastMsg, det);
addLog(g, who, "啤酒", det, -1, 0);
return 0;
case ITEM_CIG:
if (g.hp[who] < g.maxHp[who]) g.hp[who]++;
sprintf(det, "[香烟] 恢复 +1 生命 (当前%d/%d)", g.hp[who], g.maxHp[who]);
strcpy(g_lastMsg, det);
addLog(g, who, "香烟", det, -1, 0);
return 0;
case ITEM_ADREN: {
// 从对方随机偷1个道具(排除肾上腺素本身), 偷到后立即使用, 然后回合结束
int cand[NITEM], cnt2 = 0, k;
for (k = 0; k < NITEM; k++)
if (k != ITEM_ADREN && g.itemCnt[op][k] > 0) cand[cnt2++] = k;
if (cnt2 == 0) {
cnt[it]++;
sprintf(det, "对方没有可偷取的道具, 无法使用");
strcpy(g_lastMsg, det);
addLog(g, who, "肾上腺素", det, -1, 0);
return 2;
}
int pick = cand[rand() % cnt2];
g.itemCnt[op][pick]--;
g.itemCnt[who][pick]++;
sprintf(det, "偷到%s的%s, 立即使用", (op == ACT_PLAYER) ? "玩家" : "恶魔", itemName(pick));
strcpy(g_lastMsg, det);
addLog(g, who, "肾上腺素", det, -1, 0);
// 立即使用偷来的道具(不会偷到肾上腺素, 不会无限递归)
applyItem(g, who, pick);
return 1; // 回合结束
}
case ITEM_INVERT: { // 逆转器: 枪膛内剩余子弹实<->空翻转
if (g.pos < g.nShells) {
int i, c = 0;
for (i = g.pos; i < g.nShells; i++) { g.shells[i] = 1 - g.shells[i]; c++; }
sprintf(det, "翻转了剩余%d发子弹(实弹<->空弹)", c);
} else {
sprintf(det, "枪膛已空");
}
g.revealNext = -1; // 翻转后放大镜信息作废
g.phoneN = 0; // 翻转后所有电话预测的内容已变化, 全部作废
strcpy(g_lastMsg, det);
addLog(g, who, "逆转器", det, -1, 0);
return 0;
}
case ITEM_PHONE: { // 电话: 随机告知某一发未发射子弹是实还是空
if (g.pos < g.nShells) {
int idx = g.pos + rand() % (g.nShells - g.pos); // 只在剩余(未发射)子弹中选
if (g.phoneN < MAXSHELL) { // 多条电话提示全部保留, 互不覆盖
g.phonePosArr[g.phoneN] = idx;
g.phoneValArr[g.phoneN] = g.shells[idx];
g.phoneN++;
}
sprintf(det, "[电话] 第%d发是%s (提示已保留)", idx + 1, g.shells[idx] ? "实弹" : "空弹");
} else {
sprintf(det, "枪膛已空");
}
strcpy(g_lastMsg, det);
addLog(g, who, "电话", det, -1, 0);
return 0;
}
case ITEM_PILL: { // 过期药品: 等概率 +2 或 -1 生命
if (rand() % 2 == 0) {
g.hp[who] += 2;
if (g.hp[who] > g.maxHp[who]) g.hp[who] = g.maxHp[who];
sprintf(det, "[过期药品] 恢复 +2 生命 (当前%d/%d)", g.hp[who], g.maxHp[who]);
} else {
if (g.hp[who] > 0) g.hp[who]--;
sprintf(det, "[过期药品] 扣除 -1 生命 (当前%d/%d)", g.hp[who], g.maxHp[who]);
}
strcpy(g_lastMsg, det);
addLog(g, who, "过期药品", det, -1, 0);
return 0;
}
}
return 2;
}
// ---------- 界面绘制 ----------
static void printBar(int cur, int max, int len) {
int i, full = len * cur / (max > 0 ? max : 1);
if (full > len) full = len;
setColor(12);
printf("[");
for (i = 0; i < full; i++) printf("#");
setColor(8);
for (; i < len; i++) printf(".");
setColor(12);
printf("]");
setColor(7);
printf(" %d/%d", cur, max);
}
// 上帝模式: 显示枪膛内子弹真实排列
void printShellGod(const Game& g) {
int i;
setColor(13);
printf(" [上帝模式] 枪膛透视:");
setColor(7);
for (i = 0; i < g.nShells; i++) {
if (i < g.pos) {
printf(" %sx", g.shells[i] ? "实" : "空"); // 已发射
} else if (i == g.pos) {
setColor(12);
printf(" %s<", g.shells[i] ? "实" : "空"); // 当前待发
setColor(7);
} else {
setColor(10);
printf(" %s", g.shells[i] ? "实" : "空"); // 未发射
setColor(7);
}
}
printf(" (x=已发射, <=当前待发)\n");
}
void printStatus(const Game& g) {
int k;
cls();
setColor(15);
printf("================ 恶魔轮盘 ================\n");
setColor(14);
printf(" 玩家:%s 模式:%s 难度:%s 大轮%d/3 第%d次装填\n",
g.playerName, modeName(g.mode), diffName(g.difficulty), g.stage, g.roundNum);
setColor(7);
printf(" 你 HP "); printBar(g.hp[0], g.maxHp[0], 12); printf("\n");
printf(" 恶魔HP "); printBar(g.hp[1], g.maxHp[1], 12); printf("\n");
setColor(11);
printf(" 你的道具: ");
setColor(7);
{
int any = 0;
for (k = 0; k < NITEM; k++) if (g.itemCnt[0][k] > 0) {
printf("%s x%d ", itemName(k), g.itemCnt[0][k]);
any = 1;
}
if (!any) printf("(无)");
}
printf("\n");
setColor(11);
printf(" 恶魔道具: ");
setColor(7);
{
int any = 0;
for (k = 0; k < NITEM; k++) if (g.itemCnt[1][k] > 0) {
printf("%s x%d ", itemName(k), g.itemCnt[1][k]);
any = 1;
}
if (!any) printf("(无)");
}
printf("\n");
setColor(11);
printf(" 枪膛: ");
setColor(7);
if (g_showShell) // 设置开关: 实时显示本轮实弹/空弹总数
printf("本轮 %d实弹 + %d空弹 (共%d发), ", g.liveCnt, g.blankCnt, g.nShells);
printf("剩余%d发", g.nShells - g.pos);
if (g_showRemain) { // 设置开关: 辅助计算并显示剩余实弹/空弹数量
int rl = 0, i;
for (i = g.pos; i < g.nShells; i++) if (g.shells[i]) rl++;
printf(" (实弹%d 空弹%d)", rl, (g.nShells - g.pos) - rl);
}
printf(" (下一发是第%d/%d发)\n", g.pos + 1, g.nShells);
if (g.revealNext == 1) { setColor(12); printf(" [放大镜] 下一发是: 实弹!\n"); setColor(7); }
else if (g.revealNext == 0) { setColor(10); printf(" [放大镜] 下一发是: 空弹!\n"); setColor(7); }
{
int i;
for (i = 0; i < g.phoneN; i++)
if (g.phonePosArr[i] >= g.pos && g.phonePosArr[i] < g.nShells) {
setColor(11);
printf(" [电话] 第%d发是: %s\n", g.phonePosArr[i] + 1,
g.phoneValArr[i] ? "实弹" : "空弹");
setColor(7);
}
}
if (g.knifeReady) { setColor(14); printf(" [刀] 已出鞘: 下一发伤害为2点\n"); setColor(7); }
if (g.cuffed[1]) { setColor(14); printf(" [手铐] 恶魔被铐住, 下个回合将被跳过\n"); setColor(7); }
if (g.cuffed[0]) { setColor(14); printf(" [手铐] 你被铐住, 下个回合将被跳过\n"); setColor(7); }
if (g.godMode) printShellGod(g);
setColor(15);
printf(" 当前行动: %s\n", g.playerTurn ? "* 你的回合" : "> 恶魔回合");
setColor(7);
printf("==========================================\n");
}
// ---------- 恶魔 AI ----------
void dealerAction(Game& g) {
int who = ACT_DEALER;
if (g.cuffed[1]) {
g.cuffed[1] = 0;
strcpy(g_lastMsg, "恶魔被手铐铐住, 本回合被跳过!"); // 覆盖上一步残留的反馈, 避免串台
addLog(g, ACT_SYS, "手铐", "恶魔被手铐铐住, 本回合被跳过!", -1, 0);
g.playerTurn = 1; // 行动权还给玩家
return;
}
if (g.difficulty == DIFF_EASY) { // 简单: 随机开枪
int r = rand() % 10;
if (r < 4) fire(g, 1);
else if (r < 7) fire(g, 0);
else {
int items[NITEM], cnt = 0, k;
for (k = 0; k < NITEM; k++) if (g.itemCnt[who][k] > 0) items[cnt++] = k;
if (cnt > 0) {
int it = items[rand() % cnt];
int rc = applyItem(g, who, it);
if (rc == 1) {
// 肾上腺素: 偷+立即用后恶魔回合继续(与玩家一致, 控制权不交给对方)
if (g.hp[1] <= 0 || g.pos >= g.nShells) return;
dealerAction(g); // 恶魔继续行动
return;
}
if (rc == 2) fire(g, 1);
else if (g.pos >= g.nShells || g.hp[1] <= 0) return; // 啤酒弹空弹膛 / 过期药品自杀
} else fire(g, 1);
}
return;
}
if (g.difficulty == DIFF_NORMAL) { // 中等: 概率分析
if (g.itemCnt[who][ITEM_MAG] > 0) applyItem(g, who, ITEM_MAG);
int pLive = calcPLive(g);
if (g.hp[who] <= 2 && g.itemCnt[who][ITEM_CIG] > 0 && pLive < 60)
applyItem(g, who, ITEM_CIG);
if (pLive <= 35 && g.itemCnt[who][ITEM_CUFF] > 0 && !g.cuffed[0])
applyItem(g, who, ITEM_CUFF);
if (pLive >= 65 && g.itemCnt[who][ITEM_BEER] > 0) {
applyItem(g, who, ITEM_BEER);
if (g.pos >= g.nShells) return; // 啤酒弹空弹膛, 由主循环重新装填
pLive = calcPLive(g);
}
int known = g.revealNext;
if (known == 0) { // 已知空弹: 小刀对空弹无效, 打自己保回合
fire(g, 0);
} else if (known == 1) { // 已知实弹
if (g.itemCnt[who][ITEM_KNIFE] > 0) {
applyItem(g, who, ITEM_KNIFE); // 小刀: 实弹伤害翻倍
fire(g, 1);
} else fire(g, 1);
} else {
if (pLive <= 45) fire(g, 0);
else if (pLive >= 65) fire(g, 1);
else { if (rand() % 2) fire(g, 0); else fire(g, 1); }
}
return;
}
// 困难: 透视枪膛
int isLive = (g.pos < g.nShells) ? g.shells[g.pos] : -1;
if (isLive == 0) { // 空弹
if (g.hp[who] < g.maxHp[who] && g.itemCnt[who][ITEM_CIG] > 0)
applyItem(g, who, ITEM_CIG);
if (g.itemCnt[who][ITEM_CUFF] > 0 && !g.cuffed[0])
applyItem(g, who, ITEM_CUFF);
fire(g, 0); // 空弹打自己保回合 (小刀对空弹无效)
} else { // 实弹
if (g.itemCnt[who][ITEM_BEER] > 0) {
applyItem(g, who, ITEM_BEER); // 弹出实弹
if (g.pos >= g.nShells) return; // 啤酒弹空弹膛, 由主循环重新装填
} else if (g.itemCnt[who][ITEM_KNIFE] > 0) {
applyItem(g, who, ITEM_KNIFE); // 小刀: 实弹伤害翻倍
fire(g, 1);
} else {
fire(g, 1); // 打玩家
}
}
}
// ---------- 玩家回合 ----------
// 返回 0=继续 -1=放弃
int playerAction(Game& g) {
if (g.cuffed[0]) {
g.cuffed[0] = 0;
addLog(g, ACT_SYS, "手铐", "玩家被手铐铐住, 本回合被跳过!", -1, 0);
printStatus(g);
setColor(12);
printf("\n 你被手铐铐住, 本回合被跳过!\n");
setColor(7);
pause1();
g.playerTurn = 0;
return 0;
}
if (g_auto) { fire(g, 1); return 0; } // 自动测试: 玩家自动射击对方
// 全空弹/全实弹确定局面: 自动执行(设置开关)
if (g_autoCertain && g.pos < g.nShells) {
int rl = 0, i;
for (i = g.pos; i < g.nShells; i++) if (g.shells[i]) rl++;
if (rl == 0) {
addLog(g, ACT_SYS, "自动", "剩余全是空弹, 自动射击自己(安全保回合)", -1, 0);
fire(g, 0);
printStatus(g);
setColor(10);
printf(" [自动] 剩余全是空弹, 已自动射击自己(空弹无伤, 保留回合)\n");
setColor(7);
pause1();
return 0;
}
if (rl == g.nShells - g.pos) {
addLog(g, ACT_SYS, "自动", "剩余全是实弹, 自动射击对方", -1, 0);
fire(g, 1);
printStatus(g);
setColor(10);
printf(" [自动] 剩余全是实弹, 已自动射击对方\n");
setColor(7);
pause1();
return 0;
}
}
while (1) {
printStatus(g);
printf("\n 请选择行动:\n");
setColor(14);
printf(" [1] 射击自己 [2] 射击对方\n");
printf(" [3] 使用道具 [4] 保存游戏\n");
printf(" [5] 放弃本局");
if (g.godMode) {
setColor(13);
printf(" [0] 上帝视角(已开启)");
}
setColor(7);
printf("\n 输入: ");
fflush(stdout);
int c = my_getch();
printf("\n");
if (c == '1') { fire(g, 0); printLastMsg("你"); return 0; }
if (c == '2') { fire(g, 1); printLastMsg("你"); return 0; }
if (c == '3') {
int cnt = 0, k;
for (k = 0; k < NITEM; k++) if (g.itemCnt[0][k] > 0) cnt++;
if (cnt == 0) {
setColor(12); printf(" 你没有道具!\n"); setColor(7);
pause1();
continue;
}
printStatus(g);
setColor(11);
printf("\n 选择要使用的道具:\n");
for (k = 0; k < NITEM; k++)
if (g.itemCnt[0][k] > 0) printf(" [%d] %s x%d\n", k + 1, itemName(k), g.itemCnt[0][k]);
printf(" [0] 返回\n");
setColor(7);
printf(" 输入: ");
fflush(stdout);
int d = my_getch();
printf("\n");
if (d == '0') continue;
int idx = d - '1';
if (idx >= 0 && idx < NITEM && g.itemCnt[0][idx] > 0) {
if (idx == ITEM_ADREN) {
// 肾上腺素: 玩家自主选择偷哪个道具, 偷到后必须立即使用, 然后回合结束
int cand[NITEM], cnt2 = 0, k;
for (k = 0; k < NITEM; k++)
if (k != ITEM_ADREN && g.itemCnt[1][k] > 0) cand[cnt2++] = k;
if (cnt2 == 0) {
setColor(12);
printf(" 恶魔没有可偷取的道具, 无法使用肾上腺素!\n");
setColor(7);
pause1();
continue;
}
printStatus(g);
setColor(11);
printf("\n 选择要窃取的道具(偷到后立即使用):\n");
for (k = 0; k < cnt2; k++)
printf(" [%d] %s x%d\n", k + 1, itemName(cand[k]), g.itemCnt[1][cand[k]]);
printf(" [0] 返回\n 输入: ");
fflush(stdout);
int dd = my_getch();
printf("\n");
if (dd == '0') continue;
int si = dd - '1';
if (si >= 0 && si < cnt2) {
int pick = cand[si];
g.itemCnt[0][ITEM_ADREN]--; // 消耗自己的肾上腺素
g.itemCnt[1][pick]--;
g.itemCnt[0][pick]++;
setColor(14);
printf(" 偷到恶魔的%s, 立即使用!\n", itemName(pick));
setColor(7);
int rc = applyItem(g, ACT_PLAYER, pick); // 立即使用偷来的道具
if (rc == 2) printf(" (偷来的道具无法使用)\n");
else if (g_lastMsg[0]) printf(" %s\n", g_lastMsg);
pause1();
if (g.hp[0] <= 0) return 0; // 过期药品吃死自己
if (g.pos >= g.nShells) return 0; // 啤酒弹空弹膛
continue; // 偷来的道具不结束回合, 玩家回合继续(控制权不交给恶魔)
}
continue;
}
int rc = applyItem(g, ACT_PLAYER, idx);
if (rc == 2) { setColor(12); printf(" 道具无法使用!\n"); setColor(7); pause1(); }
else if (g_lastMsg[0]) { setColor(14); printf(" %s\n", g_lastMsg); setColor(7); pause1(); }
if (g.hp[0] <= 0) return 0; // 过期药品把自己吃死了, 交给主循环判定
if (rc == 1) { g.playerTurn = 0; return 0; } // 肾上腺素(AI偷取路径等): 回合结束
if (g.pos >= g.nShells) return 0; // 啤酒喝空弹膛, 交给主循环重新装填
}
continue;
}
if (c == '4') {
setColor(11);
printf(" 保存到哪个存档槽(1-%d)? [0取消]: ", MAXSLOT);
setColor(7);
fflush(stdout);
int s = my_getch();
printf("\n");
if (s >= '1' && s <= '0' + MAXSLOT) {
int slot = s - '0';
if (saveSlot(g, slot) == 0) {
setColor(10); printf(" [OK] 已保存到存档槽 %d\n", slot); setColor(7);
} else {
setColor(12); printf(" 保存失败!\n"); setColor(7);
}
}
pause1();
continue;
}
if (c == '5') {
setColor(12);
printf(" 确认放弃本局? (按 Y 确认, 其他键继续): ");
setColor(7);
fflush(stdout);
int y = my_getch();
printf("\n");
if (y == 'y' || y == 'Y') return -1;
continue;
}
if (c == '0' && g.godMode) { printStatus(g); pause1(); continue; }
}
}
// ---------- 计分 ----------
void computeScore(Game& g) {
int s = 0, k;
if (g.over == 2) { g.score = 0; return; } // 放弃本局
if (g.winner == 0) s += 200 + g.stageWins[0] * 40; // 总胜利大奖 + 每赢一轮
else s += 15 + g.stageWins[1] * 10;
if (g.winner >= 0 && g.hp[g.winner] > 0) s += g.hp[g.winner] * 10;
for (k = 0; k < NITEM; k++) s += (g.itemCnt[0][k] + g.itemCnt[1][k]) * 2;
s += g.blanksSelfHit * 5;
s += g.roundNum * 6;
g.score = s;
}
void printResult(const Game& g) {
cls();
setColor(15);
printf("================ 对局结束 ================\n");
setColor(7);
if (g.over == 2) {
setColor(12);
printf(" 你放弃了本局!\n");
} else if (g.winner == 0) {
setColor(10);
printf(" ***** 你赢了! *****\n");
printf(" 恶魔被击败, 你活了下来!\n");
} else {
setColor(12);
printf(" X 你输了... 恶魔取得了胜利\n");
}
setColor(7);
printf("\n 对局统计:\n");
printf(" 玩家: %s (HP %d/%d) 恶魔: (HP %d/%d)\n",
g.playerName, g.hp[0], g.maxHp[0], g.hp[1], g.maxHp[1]);
printf(" 模式: %s 难度: %s 大轮比分: 玩家%d : %d恶魔\n",
modeName(g.mode), diffName(g.difficulty), g.stageWins[0], g.stageWins[1]);
printf(" 总装填次数: %d 空弹打自己(赚回合): %d 次\n", g.roundNum, g.blanksSelfHit);
printf(" 总行动次数: %d\n", g.logLen);
setColor(14);
printf(" 本局得分: %d\n", g.score);
setColor(7);
printf("==========================================\n");
}
void godReview(const Game& g) {
int i, k;
cls();
setColor(13);
printf("============= 上帝模式 - 复盘 =============\n");
setColor(7);
printf(" 对局时间: %s\n 玩家: %s 模式:%s 难度:%s 结果:%s 得分:%d\n",
g.matchTime, g.playerName, modeName(g.mode), diffName(g.difficulty),
g.winner == 0 ? "玩家胜" : (g.winner == 1 ? "恶魔胜" : "放弃"), g.score);
printf(" 大轮比分: 玩家 %d : %d 恶魔 总装填 %d 次\n",
g.stageWins[0], g.stageWins[1], g.roundNum);
printf("\n [枪膛内子弹真实排列] (x=已发射, <=下一发)\n ");
for (i = 0; i < g.nShells; i++) {
if (i < g.pos) printf(" %sx", g.shells[i] ? "实" : "空");
else if (i == g.pos) printf(" %s<", g.shells[i] ? "实" : "空");
else printf(" %s", g.shells[i] ? "实" : "空");
}
printf("\n 已发射 %d/%d 发, 剩余 %d 发\n", g.pos, g.nShells, g.nShells - g.pos);
printf("\n [双方最终道具]\n");
printf(" 你 : ");
{
int any = 0;
for (k = 0; k < NITEM; k++) if (g.itemCnt[0][k] > 0) { printf("%s x%d ", itemName(k), g.itemCnt[0][k]); any = 1; }
if (!any) printf("(无)");
}
printf("\n 恶魔: ");
{
int any = 0;
for (k = 0; k < NITEM; k++) if (g.itemCnt[1][k] > 0) { printf("%s x%d ", itemName(k), g.itemCnt[1][k]); any = 1; }
if (!any) printf("(无)");
}
printf("\n");
printf("\n [每轮装填历史]\n");
for (i = 0; i < g.logLen; i++)
if (g.log[i].actor == ACT_SYS && strncmp(g.log[i].act, "开始新轮", 8) == 0)
printf(" 第%d轮: %s\n", g.log[i].round, g.log[i].detail);
printf("\n [完整行动日志]\n");
for (i = 0; i < g.logLen; i++) {
const LogEntry& L = g.log[i];
printf(" R%-2d %-6s %-12s %s\n", L.round, actorName(L.actor), L.act, L.detail);
}
setColor(13);
printf("==========================================\n");
setColor(7);
pause1();
}
void printStageEnd(const Game& g, int roundWinner) {
if (g_auto) return; // 自动测试: 跳过按键等待
cls();
setColor(15);
printf("============== 第%d/3大轮结束 ==============\n", g.stage);
setColor(7);
if (roundWinner == 0) { setColor(10); printf(" ***** 你赢了本轮! *****\n"); }
else { setColor(12); printf(" X 你输了本轮...\n"); }
setColor(7);
printf(" 当前比分: 玩家 %d : %d 恶魔\n", g.stageWins[0], g.stageWins[1]);
if (g.stage >= 3) {
setColor(14);
printf(" 三场已全部打完, 按总比分判定本局胜负!\n");
} else {
setColor(11);
printf(" 下一轮双方生命将重置, 重新装填子弹...\n");
}
setColor(15);
printf("==========================================\n");
setColor(7);
pause1();
}
void runMatch(Game& g) {
// 一局固定3大轮: 每轮重置生命并重新装填(数量随机并告知), 打满3轮后按总比分判定胜负
int freshStage = !g_resume; // 读取存档继续时保留当前大轮生命, 否则每大轮开始重置
g_resume = 0;
while (g.stage <= 3 && g.over != 2) {
if (freshStage) {
g.hp[0] = g.maxHp[0]; // 每大轮重置生命
g.hp[1] = g.maxHp[1];
g.blanksSelfHit = 0;
}
newRound(g); // 大轮开始装填
int roundWinner = -1;
while (1) {
if (g.hp[0] <= 0) { roundWinner = 1; break; }
if (g.hp[1] <= 0) { roundWinner = 0; break; }
if (g.pos >= g.nShells) { newRound(g); continue; }
if (g.playerTurn) {
int r = playerAction(g);
if (r == -1) { g.over = 2; break; }
} else {
printStatus(g);
setColor(15);
printf("\n 恶魔正在思考...\n");
setColor(7);
pause1();
dealerAction(g);
printLastMsg("恶魔"); // 明确显示恶魔本次操作及结果
}
}
if (g.over == 2) break;
g.stageWins[roundWinner]++;
printStageEnd(g, roundWinner);
g.stage++; // 打满3大轮, 不提前结束
freshStage = 1;
}
if (g.over == 2) g.winner = -1;
else {
g.winner = (g.stageWins[0] > g.stageWins[1]) ? 0 : 1;
g.over = 1;
}
computeScore(g);
addScoreRecord(g);
saveMatch(g);
printResult(g);
while (1) {
if (g_auto) break; // 自动测试: 跳过赛后菜单
setColor(14);
printf("\n [1] 上帝模式复盘(查看枪膛内子弹等) [2] 返回主菜单\n");
setColor(7);
printf(" 输入: ");
fflush(stdout);
int c = my_getch();
printf("\n");
if (c == '1') { godReview(g); break; }
if (c == '2') break;
}
}
// ============================================================
// 存档 / 回放 / 分数
// ============================================================
int saveSlot(Game& g, int slot) {
if (slot < 1 || slot > MAXSLOT) return -1;
char fn[64];
sprintf(fn, "slot%d.brs", slot);
FILE* f = fopen(fn, "wb");
if (!f) return -1;
fwrite(&g, 1, sizeof(Game), f);
fclose(f);
return 0;
}
int loadSlot(Game& g, int slot) {
if (slot < 1 || slot > MAXSLOT) return -1;
char fn[64];
sprintf(fn, "slot%d.brs", slot);
FILE* f = fopen(fn, "rb");
if (!f) return -1;
fread(&g, 1, sizeof(Game), f);
fclose(f);
if (strncmp(g.magic, "BRSAVE5", 7) != 0) return -1;
return 0;
}
void saveMatch(Game& g) {
ensureDirs();
char fname[48], file[80], line[256];
strcpy(fname, g.matchTime);
{
int i;
for (i = 0; fname[i]; i++)
if (fname[i] == ' ' || fname[i] == ':' || fname[i] == '-')
fname[i] = '_';
}
sprintf(file, "matches/match_%s.brs", fname);
FILE* f = fopen(file, "wb");
if (!f) return;
fwrite(&g, 1, sizeof(Game), f);
fclose(f);
FILE* idx = fopen("matches/index.txt", "a");
if (idx) {
sprintf(line, "%s|%s|%s|%s|%s|%s|%d\n",
file, g.matchTime, g.playerName, modeName(g.mode),
diffName(g.difficulty),
g.over == 2 ? "放弃" : (g.winner == 0 ? "胜" : "负"), g.score);
fputs(line, idx);
fclose(idx);
}
}
void addScoreRecord(const Game& g) {
FILE* f = fopen("scores.txt", "a");
if (!f) return;
fprintf(f, "%s|%s|%s|%s|%s|%d\n", g.matchTime, g.playerName,
modeName(g.mode), diffName(g.difficulty),
g.over == 2 ? "放弃" : (g.winner == 0 ? "胜" : "负"), g.score);
fclose(f);
}
void showScores() {
struct Rec { char time[24]; char name[24]; char mode[16]; char diff[16]; char res[8]; int score; };
Rec rec[256];
int n = 0;
FILE* f = fopen("scores.txt", "r");
if (f) {
char line[256];
while (n < 256 && fgets(line, sizeof(line), f)) {
char t[24], nm[24], md[16], df[16], rs[8];
int sc;
if (sscanf(line, "%[^|]|%[^|]|%[^|]|%[^|]|%[^|]|%d", t, nm, md, df, rs, &sc) == 6) {
strcpy(rec[n].time, t); strcpy(rec[n].name, nm);
strcpy(rec[n].mode, md); strcpy(rec[n].diff, df);
strcpy(rec[n].res, rs); rec[n].score = sc;
n++;
}
}
fclose(f);
}
if (n == 0) {
cls();
setColor(14);
printf("==== 分数排行榜 ====\n");
setColor(7);
printf(" 暂无对局记录, 快去玩一局吧!\n");
pause1();
return;
}
int i, j;
for (i = 0; i < n - 1; i++)
for (j = 0; j < n - 1 - i; j++)
if (rec[j].score < rec[j + 1].score) {
Rec t = rec[j]; rec[j] = rec[j + 1]; rec[j + 1] = t;
}
cls();
setColor(14);
printf("===== 分数排行榜 (Top %d) =====\n", n > 20 ? 20 : n);
setColor(7);
printf(" 名次 得分 玩家 时间 模式 难度 结果\n");
printf(" ----------------------------------------------------------------\n");
int top = n > 20 ? 20 : n;
for (i = 0; i < top; i++)
printf(" %-4d %-5d %-15s %-20s %-4s %-4s %s\n",
i + 1, rec[i].score, rec[i].name, rec[i].time,
rec[i].mode, rec[i].diff, rec[i].res);
printf(" ----------------------------------------------------------------\n");
pause1();
}
void replayMatch(const char* file) {
Game g;
FILE* f = fopen(file, "rb");
if (!f) { setColor(12); printf(" 读取回放失败!\n"); setColor(7); pause1(); return; }
fread(&g, 1, sizeof(Game), f);
fclose(f);
if (strncmp(g.magic, "BRSAVE5", 7) != 0) {
setColor(12); printf(" 回放文件无效!\n"); setColor(7); pause1(); return;
}
int i = 0, god = 0;
while (1) {
cls();
setColor(14);
printf("===== 对局回放 =====\n");
setColor(7);
printf(" 玩家:%s 模式:%s 难度:%s 时间:%s 得分:%d\n",
g.playerName, modeName(g.mode), diffName(g.difficulty), g.matchTime, g.score);
printf(" 大轮比分: 玩家 %d : %d 恶魔 结果: %s\n",
g.stageWins[0], g.stageWins[1],
g.over == 2 ? "放弃" : (g.winner == 0 ? "玩家胜" : "恶魔胜"));
printf(" --------------------------------------------------\n");
int showFrom = (i - 12 > 0) ? i - 12 : 0;
int j;
for (j = showFrom; j <= i && j < g.logLen; j++) {
const LogEntry& L = g.log[j];
if (j == i) setColor(10);
printf(" R%-2d %-6s %-12s %s\n", L.round, actorName(L.actor), L.act, L.detail);
if (j == i) setColor(7);
}
if (god) {
setColor(13);
printf(" [上帝模式] 枪膛: ");
setColor(7);
int kk;
for (kk = 0; kk < g.nShells; kk++)
printf("%s ", kk < g.pos ? (g.shells[kk] ? "实x" : "空x") : (g.shells[kk] ? "实" : "空"));
printf("\n");
}
printf(" --------------------------------------------------\n");
setColor(11);
printf(" 动作 %d/%d 空格/回车:下一步 G:上帝模式 Q:退出\n", i + 1, g.logLen);
setColor(7);
int c = my_getch();
if (c == ' ' || c == '\r' || c == '\n') { if (i < g.logLen - 1) i++; }
else if (c == 'g' || c == 'G') god = !god;
else if (c == 'q' || c == 'Q') break;
}
}
void selectReplay() {
FILE* f = fopen("matches/index.txt", "r");
if (!f) {
cls();
setColor(14); printf("==== 回放对局 ====\n");
setColor(7);
printf(" 暂无对局记录!\n");
pause1();
return;
}
char files[256][96];
int n = 0;
char line[256];
while (n < 256 && fgets(line, sizeof(line), f)) {
char f1[96], t[24], nm[24], md[16], df[16], rs[8]; int sc;
if (sscanf(line, "%[^|]|%[^|]|%[^|]|%[^|]|%[^|]|%[^|]|%d", f1, t, nm, md, df, rs, &sc) == 7) {
strcpy(files[n], f1);
n++;
}
}
fclose(f);
if (n == 0) {
cls();
setColor(14); printf("==== 回放对局 ====\n");
setColor(7);
printf(" 暂无对局记录!\n");
pause1();
return;
}
cls();
setColor(14);
printf("==== 选择要回放的对局 ====\n");
setColor(7);
FILE* f2 = fopen("matches/index.txt", "r");
if (f2) {
char lines[256][256];
int cnt = 0;
while (cnt < 256 && fgets(lines[cnt], sizeof(lines[cnt]), f2)) cnt++;
fclose(f2);
int i;
for (i = cnt - 1; i >= 0; i--) {
char f1[96], t[24], nm[24], md[16], df[16], rs[8]; int sc;
if (sscanf(lines[i], "%[^|]|%[^|]|%[^|]|%[^|]|%[^|]|%[^|]|%d", f1, t, nm, md, df, rs, &sc) == 7)
printf(" [%d] %s %s %s-%s %s %d分\n", cnt - i, t, nm, md, df, rs, sc);
}
}
printf(" [0] 返回\n 输入: ");
fflush(stdout);
int sel = readNum(); // 支持多位数字(第10局起也能正常选择)
if (sel >= 1 && sel <= n) {
replayMatch(files[n - sel]); // 列表编号1=最新一局
}
}
// ============================================================
// 菜单 / 帮助 / 入口
// ============================================================
void help() {
cls();
setColor(14);
printf("============== 恶魔轮盘 - 规则说明 ==============\n");
setColor(7);
printf("\n* 玩法\n");
printf(" 一局共3大轮。每大轮开始时双方生命重置, 向霰弹枪装填若干实弹\n");
printf(" 和空弹(数量随机), 双方轮流行动:\n");
printf(" - 射击自己: 实弹->自己掉血, 行动权移交; 空弹->毫发无损, 保留行动权(赚回合)\n");
printf(" - 射击对方: 实弹->对方掉血, 行动权移交对方; 空弹->白送对方回合\n");
printf(" 大轮内谁生命先归零谁输本轮, 率先赢下2轮者赢得整局。\n");
printf(" 装弹时会告知本轮实弹/空弹总数, 界面实时显示(可在[6]设置中关闭)。\n");
printf("\n* 道具(每大轮随机发2个)\n");
printf(" 放大镜: 查看当前下一发是实弹还是空弹, 信息在本回合内一直显示\n");
printf(" 小刀: 下一发子弹伤害翻倍(空包弹伤害仍为0)\n");
printf(" 手铐: 跳过对方下一个回合\n");
printf(" 啤酒: 退出当前下一发子弹(实空都弹掉)\n");
printf(" 香烟: 恢复1点生命(使用时会显示恢复血量)\n");
printf(" 肾上腺素: 自主选择偷对方1个道具并立即使用(不能偷肾上腺素), 回合结束\n");
printf(" 逆转器: 将枪膛内剩余所有子弹翻转(实弹<->空弹)\n");
printf(" 电话: 只预测某一发未发射的子弹, 该发被发射后才失效\n");
printf(" 过期药品: 等概率恢复2点或扣除1点生命(会显示恢复/扣除血量)\n");
printf("\n* 模式\n");
printf(" 经典: 每轮随机发2个道具\n");
printf(" 开局供给: 第1轮每人4个道具, 之后每轮2个\n");
printf(" 无道具: 纯对拼, 没有道具\n");
printf(" 无限道具: 每轮道具补满到8个\n");
printf("\n* 难度\n");
printf(" 简单: 恶魔随机开枪 (双方各4血)\n");
printf(" 中等: 恶魔计算剩余实弹概率, 分析后做选择 (双方各4血)\n");
printf(" 困难: 恶魔直接透视枪膛, 知道每一发子弹是实是空 (双方各4血)\n");
printf("\n* 其他功能\n");
printf(" 局中[4]可保存到1~9号存档槽; 每局结束自动记录得分并可进入\n");
printf(" 上帝模式复盘查看枪膛内真实子弹排列; 主菜单可回放历史对局。\n");
printf(" 装填配置: 每大轮子弹数与实/空比例全部随机, 但保证至少1实弹和1空弹;\n");
printf(" 每大轮装弹后必定告知本轮实弹与空弹的数量。\n");
printf(" 主菜单[6]设置: 可开关'实时显示子弹总数'、'剩余实/空弹辅助计算'、\n");
printf(" '全空/全实自动执行'(全空自动打自己, 全实自动打对方)三个辅助功能。\n");
printf("==================================================\n");
pause1();
}
int modeSelect() {
while (1) {
cls();
setColor(14);
printf("==== 选择模式 ====\n");
setColor(7);
printf(" [1] 经典 (每轮随机发2个道具)\n");
printf(" [2] 开局供给 (第1轮每人4个道具, 之后每轮2个)\n");
printf(" [3] 无道具 (纯对拼)\n");
printf(" [4] 无限道具 (每轮道具补满到8个)\n");
printf(" [0] 返回\n 输入: ");
fflush(stdout);
int c = my_getch();
printf("\n");
if (c == '1') return MODE_CLASSIC;
if (c == '2') return MODE_HEADSTART;
if (c == '3') return MODE_NOITEM;
if (c == '4') return MODE_INFINITE;
if (c == '0') return -1;
}
}
int diffSelect() {
while (1) {
cls();
setColor(14);
printf("==== 选择难度 ====\n");
setColor(7);
printf(" [1] 简单 (恶魔随机开枪, 双方各4血)\n");
printf(" [2] 中等 (恶魔概率分析选择, 双方各4血)\n");
printf(" [3] 困难 (恶魔透视枪膛内子弹, 双方各4血)\n");
printf(" [0] 返回\n 输入: ");
fflush(stdout);
int c = my_getch();
printf("\n");
if (c == '1') return DIFF_EASY;
if (c == '2') return DIFF_NORMAL;
if (c == '3') return DIFF_HARD;
if (c == '0') return -1;
}
}
void settingsMenu() {
while (1) {
cls();
setColor(14);
printf("==== 设置 ====\n");
setColor(7);
printf(" [1] 实时显示本轮子弹总数(实弹/空弹): %s\n",
g_showShell ? "开" : "关");
printf(" (关闭后仅装填瞬间告知总数, 界面不再持续显示)\n");
printf(" [2] 辅助计算剩余实弹/空弹数量: %s\n",
g_showRemain ? "开" : "关");
printf(" (实时帮你看清还剩几发真弹几发空弹)\n");
printf(" [3] 自动执行全空弹/全实弹确定局面: %s\n",
g_autoCertain ? "开" : "关");
printf(" (剩余全是空弹自动打自己保回合, 全是实弹自动打对方)\n");
printf(" [0] 返回\n 输入: ");
fflush(stdout);
int c = my_getch();
printf("\n");
if (c == '1') g_showShell = !g_showShell;
if (c == '2') g_showRemain = !g_showRemain;
if (c == '3') g_autoCertain = !g_autoCertain;
if (c == '0') return;
}
}
int mainMenu() {
while (1) {
cls();
setColor(15);
printf("==========================================\n");
setColor(12);
printf(" ####### 恶 魔 轮 盘 #######\n");
setColor(7);
printf(" Buckshot Roulette (文字版)\n");
printf("==========================================\n");
setColor(14);
printf(" [1] 开始新对局\n");
printf(" [2] 读取存档(继续对局)\n");
printf(" [3] 回放历史对局\n");
printf(" [4] 分数排行榜\n");
printf(" [5] 游戏帮助(规则)\n");
printf(" [6] 设置\n");
printf(" [0] 退出\n");
setColor(7);
printf(" 输入: ");
fflush(stdout);
int c = my_getch();
printf("\n");
if (c == '1') {
int mode = modeSelect();
if (mode < 0) continue;
int diff = diffSelect();
if (diff < 0) continue;
char name[24];
cls();
printf(" 输入你的名字(直接回车=玩家): ");
fflush(stdout);
int i = 0;
int ch = getchar();
while (i < 23 && ch != '\n' && ch != '\r') { name[i++] = (char)ch; ch = getchar(); }
name[i] = 0;
while (ch != '\n' && ch != '\r' && (ch = getchar()) != '\n');
if (i == 0) strcpy(name, "玩家");
Game g;
resetGame(g, mode, diff, name);
runMatch(g);
} else if (c == '2') {
cls();
setColor(14);
printf("==== 读取存档 ====\n");
setColor(7);
int s;
for (s = 1; s <= MAXSLOT; s++) {
char fn[64];
sprintf(fn, "slot%d.brs", s);
FILE* f = fopen(fn, "rb");
if (f) {
Game g;
fread(&g, 1, sizeof(Game), f);
fclose(f);
if (strncmp(g.magic, "BRSAVE5", 7) == 0)
printf(" [%d] %s 模式:%s 难度:%s 第%d轮 HP:%d/%d\n",
s, g.playerName, modeName(g.mode), diffName(g.difficulty),
g.roundNum, g.hp[0], g.maxHp[0]);
else
printf(" [%d] (损坏)\n", s);
} else {
printf(" [%d] (空)\n", s);
}
}
printf(" [0] 返回\n 输入: ");
fflush(stdout);
int sel = my_getch();
printf("\n");
if (sel >= '1' && sel <= '0' + MAXSLOT) {
Game g;
int slot = sel - '0';
if (loadSlot(g, slot) == 0) {
printf(" 已读取存档槽 %d, 继续对局!\n", slot);
pause1();
g_resume = 1; // 存档继续: 保留当前大轮生命
runMatch(g);
} else {
printf(" 读取失败!\n");
pause1();
}
}
} else if (c == '3') {
selectReplay();
} else if (c == '4') {
showScores();
} else if (c == '5') {
help();
} else if (c == '6') {
settingsMenu();
} else if (c == '0') {
cls();
setColor(12);
printf(" 感谢游玩恶魔轮盘, 再见!\n");
setColor(7);
break;
}
}
return 0;
}
// ---------- 自动测试(仅命令行参数 autotest 时运行, 用于验证) ----------
void autoPlayer(Game& g) {
int r = rand() % 10;
if (r < 5) fire(g, 1);
else fire(g, 0);
}
void runAutoTest() {
int m;
for (m = 0; m < 4; m++) {
int mode = 1 + m;
int diff = (m == 0) ? DIFF_EASY : ((m == 1) ? DIFF_NORMAL : DIFF_HARD);
int wins[2] = {0, 0};
int t;
for (t = 0; t < 30; t++) {
Game g;
resetGame(g, mode, diff, "AutoTest");
newRound(g);
int guard = 0;
while (guard++ < 2000) {
if (g.hp[0] <= 0) { g.over = 1; g.winner = 1; break; }
if (g.hp[1] <= 0) { g.over = 1; g.winner = 0; break; }
if (g.pos >= g.nShells) { newRound(g); continue; }
if (g.playerTurn) autoPlayer(g);
else dealerAction(g);
}
if (g.winner == 0) wins[0]++; else if (g.winner == 1) wins[1]++;
}
printf("模式=%s 难度=%s: 玩家胜%d 恶魔胜%d\n",
modeName(mode), diffName(diff), wins[0], wins[1]);
}
printf("AutoTest 完成(无崩溃)。\n");
}
// 全流程测试: 跑完整 runMatch(含结算/存档/记录), 验证文件真实读写
void runFlowTest() {
g_auto = 1;
g_noPause = 1;
int m, ok = 1;
remove("scores.txt");
remove("matches/index.txt");
for (m = 0; m < 4; m++) {
Game g;
int mode = 1 + m;
int diff = (m == 0) ? DIFF_EASY : ((m == 1) ? DIFF_NORMAL : DIFF_HARD);
resetGame(g, mode, diff, "FlowTest");
runMatch(g);
if (g.over != 1) { printf(" 第%d局: 对局未正常结束 over=%d\n", m + 1, g.over); ok = 0; }
if (saveSlot(g, 1) != 0) { printf(" saveSlot FAIL\n"); ok = 0; }
Game g2;
if (loadSlot(g2, 1) != 0 || g2.score != g.score || g2.roundNum != g.roundNum || g2.hp[0] != g.hp[0]) {
printf(" loadSlot 往返不一致\n"); ok = 0;
} else {
printf(" 第%d局 OK: %s 难度=%s 结果=%s 得分=%d\n",
m + 1, modeName(mode), diffName(diff),
g.winner == 0 ? "胜" : "负", g.score);
}
}
int lines = 0;
FILE* f = fopen("matches/index.txt", "r");
if (f) { char b[512]; while (fgets(b, sizeof(b), f)) lines++; fclose(f); }
if (lines != 4) { printf(" matches/index.txt 行数=%d (期望4)\n", lines); ok = 0; }
else printf(" matches/index.txt 行数=%d OK\n", lines);
int sl = 0;
f = fopen("scores.txt", "r");
if (f) { char b[512]; while (fgets(b, sizeof(b), f)) sl++; fclose(f); }
if (sl != 4) { printf(" scores.txt 行数=%d (期望4)\n", sl); ok = 0; }
else printf(" scores.txt 行数=%d OK\n", sl);
f = fopen("matches/index.txt", "r");
if (f) {
char line[256], file[96];
if (fgets(line, sizeof(line), f)) {
int p = 0;
while (line[p] && line[p] != '|') { file[p] = line[p]; p++; }
file[p] = 0;
Game r;
FILE* rf = fopen(file, "rb");
if (rf) {
fread(&r, 1, sizeof(Game), rf);
fclose(rf);
if (strncmp(r.magic, "BRSAVE5", 7) != 0 || r.logLen <= 0) { printf(" 回放文件读取异常\n"); ok = 0; }
else printf(" 回放文件OK: %s 日志%d条\n", file, r.logLen);
} else { printf(" 回放文件不存在: %s\n", file); ok = 0; }
}
fclose(f);
} else { printf(" matches/index.txt 无法读取\n"); ok = 0; }
{
Game g; resetGame(g, MODE_CLASSIC, DIFF_EASY, "FlowTest");
newRound(g);
g.pos = 1;
godReview(g);
printf(" 上帝模式复盘 OK\n");
}
printf(ok ? "\nFlowTest 全部通过\n" : "\nFlowTest 存在失败项\n");
}
// 新道具单元测试(仅 itemtest 参数触发)
void runItemTest() {
Game g;
resetGame(g, MODE_CLASSIC, DIFF_NORMAL, "ItemTest");
newRound(g);
int ok = 1;
// --- 逆转器 ---
int before[MAXSHELL], i;
memcpy(before, g.shells, sizeof(before));
g.itemCnt[0][ITEM_INVERT] = 1;
int rc = applyItem(g, 0, ITEM_INVERT);
if (rc != 0) { printf("逆转器返回值错误\n"); ok = 0; }
for (i = g.pos; i < g.nShells; i++)
if (g.shells[i] != 1 - before[i]) { printf("逆转器翻转错误 pos=%d\n", i); ok = 0; }
printf("逆转器: %s (剩余%d发已实<->空翻转)\n", ok ? "OK" : "FAIL", g.nShells - g.pos);
// --- 电话 ---
g.itemCnt[0][ITEM_PHONE] = 1;
rc = applyItem(g, 0, ITEM_PHONE);
if (rc != 0 || g.phoneN != 1 || g.phonePosArr[0] < g.pos || g.phonePosArr[0] >= g.nShells) {
printf("电话位置错误\n"); ok = 0;
}
else if (g.phoneValArr[0] != g.shells[g.phonePosArr[0]]) { printf("电话值错误\n"); ok = 0; }
printf("电话: %s (告知第%d发=%s)\n", ok ? "OK" : "FAIL",
g.phonePosArr[0] + 1, g.phoneValArr[0] ? "实弹" : "空弹");
// --- 多个电话: 连续使用多个手机, 所有提示都应保留 ---
{
g.phoneN = 0; // 清空再测
g.itemCnt[0][ITEM_PHONE] = 2;
rc = applyItem(g, 0, ITEM_PHONE);
if (rc != 0) { printf("第2个电话使用失败\n"); ok = 0; }
rc = applyItem(g, 0, ITEM_PHONE);
if (rc != 0) { printf("第3个电话使用失败\n"); ok = 0; }
if (g.phoneN != 2) { printf("连续使用多个手机应全部保留提示, 实际%d条\n", g.phoneN); ok = 0; }
int i2;
for (i2 = 0; i2 < g.phoneN; i2++)
if (g.phonePosArr[i2] < g.pos || g.phonePosArr[i2] >= g.nShells ||
g.phoneValArr[i2] != g.shells[g.phonePosArr[i2]]) {
printf("多条电话提示内容错误 i=%d\n", i2); ok = 0;
}
printf("多电话保留: %s (连续%d个手机全部保留)\n", ok ? "OK" : "FAIL", g.phoneN);
}
// --- 过期药品 ---
g.hp[0] = 1;
g.maxHp[0] = 5;
int up = 0, down = 0, t;
for (t = 0; t < 200; t++) {
int bh = g.hp[0];
g.itemCnt[0][ITEM_PILL] = 1;
rc = applyItem(g, 0, ITEM_PILL);
if (rc != 0) { printf("药品返回值错误\n"); ok = 0; }
if (g.hp[0] == bh + 2) up++;
else if (g.hp[0] == bh - 1) down++;
else { printf("药品结果异常 %d->%d\n", bh, g.hp[0]); ok = 0; }
g.hp[0] = 1;
}
printf("过期药品: %s (恢复+2:%d次 扣除-1:%d次)\n", ok ? "OK" : "FAIL", up, down);
// --- 小刀: 空弹伤害仍为0 ---
newRound(g);
if (g.pos < g.nShells && g.shells[g.pos] == 0) {
g.knifeReady = 0;
g.itemCnt[0][ITEM_KNIFE] = 1;
applyItem(g, 0, ITEM_KNIFE);
int dmg = fire(g, 1);
if (dmg != 0) { printf("小刀对空弹应造成0伤害, 实际%d\n", dmg); ok = 0; }
printf("小刀空弹测试: %s (空弹伤害=%d)\n", ok ? "OK" : "FAIL", dmg);
}
// --- 电话: 预测的子弹被发射后才消失 ---
{
Game t;
resetGame(t, MODE_CLASSIC, DIFF_NORMAL, "ItemTest");
newRound(t);
if (t.nShells >= 3) {
t.phoneN = 0;
t.phonePosArr[0] = t.pos + 2; // 电话预测"再往后2发"的位置
t.phoneValArr[0] = t.shells[t.phonePosArr[0]];
t.phoneN = 1;
int before = t.phonePosArr[0];
int firedPos0 = t.pos;
fire(t, 1); // 发射的是下一发(非预测位置)
if (t.phoneN == 1 && t.phonePosArr[0] == before) {
printf("电话延迟: OK (发射第%d发后, 预测的第%d发仍显示)\n", firedPos0 + 1, before + 1);
} else {
printf("电话应在预测子弹发射后才消失, 提前消失了\n"); ok = 0;
}
while (t.pos <= before && t.pos < t.nShells) fire(t, 1); // 一直发射到越过预测位置
if (t.pos > before && t.phoneN == 0)
printf("电话延迟: OK (预测的第%d发被发射后消失)\n", before + 1);
else { printf("电话应在预测子弹发射后消失\n"); ok = 0; }
} else {
printf("电话延迟: 跳过(本轮子弹不足3发)\n");
}
}
// --- 肾上腺素: 自主偷取(排除肾上腺素)、偷到后立即使用 ---
{
Game t;
resetGame(t, MODE_CLASSIC, DIFF_NORMAL, "ItemTest");
// 恶魔: 香烟x1 + 肾上腺素x2; 玩家: 肾上腺素x1, HP=1(允许香烟立即生效)
t.hp[0] = 1;
t.itemCnt[1][ITEM_CIG] = 1;
t.itemCnt[1][ITEM_ADREN] = 2;
t.itemCnt[0][ITEM_ADREN] = 1;
int rc = applyItem(t, 0, ITEM_ADREN);
if (rc != 1) { printf("肾上腺素应正常使用(返回1), 实际%d\n", rc); ok = 0; }
// 偷到香烟并立即使用: 玩家香烟被消耗(0), HP 1->2, 恶魔香烟 0
if (t.itemCnt[0][ITEM_CIG] != 0 || t.itemCnt[1][ITEM_CIG] != 0) {
printf("未偷到香烟或未立即使用: 玩家=%d 恶魔=%d\n",
t.itemCnt[0][ITEM_CIG], t.itemCnt[1][ITEM_CIG]);
ok = 0;
}
if (t.hp[0] != 2) { printf("偷到的香烟未立即生效, HP=%d\n", t.hp[0]); ok = 0; }
if (t.itemCnt[0][ITEM_ADREN] != 0 || t.itemCnt[1][ITEM_ADREN] != 2) {
printf("肾上腺素不应被偷取(无法盗取肾上腺素), 玩家=%d 恶魔=%d\n",
t.itemCnt[0][ITEM_ADREN], t.itemCnt[1][ITEM_ADREN]);
ok = 0;
}
// 对方只有肾上腺素时, 使用应无效
t.itemCnt[0][ITEM_ADREN] = 1;
t.itemCnt[1][ITEM_CIG] = 0;
rc = applyItem(t, 0, ITEM_ADREN);
if (rc != 2) { printf("对方只有肾上腺素时应无法使用(返回2), 实际%d\n", rc); ok = 0; }
if (t.itemCnt[0][ITEM_ADREN] != 1) { printf("无效使用后肾上腺素应返还\n"); ok = 0; }
printf("肾上腺素: %s (自主偷取、立即使用、排除肾上腺素)\n", ok ? "OK" : "FAIL");
}
// --- 啤酒: 明确显示弹出了第几发、什么子弹 ---
{
Game t;
resetGame(t, MODE_CLASSIC, DIFF_NORMAL, "ItemTest");
newRound(t);
t.itemCnt[0][ITEM_BEER] = 1;
int nBefore = t.nShells - t.pos;
int rc = applyItem(t, 0, ITEM_BEER);
if (rc != 0 || (t.nShells - t.pos) != nBefore - 1) { printf("啤酒未正确弹出\n"); ok = 0; }
if (strstr(g_lastMsg, "弹出了第") == NULL ||
(strstr(g_lastMsg, "实弹") == NULL && strstr(g_lastMsg, "空弹") == NULL)) {
printf("啤酒文案不明确: %s\n", g_lastMsg); ok = 0;
}
printf("啤酒: %s (%s)\n", ok ? "OK" : "FAIL", g_lastMsg);
}
// --- 全空弹/全实弹自动执行 ---
{
Game t;
resetGame(t, MODE_CLASSIC, DIFF_EASY, "ItemTest");
newRound(t);
int oldAuto = g_auto, oldPause = g_noPause, oldCert = g_autoCertain;
g_auto = 0; g_noPause = 1; g_autoCertain = 1;
int i;
// 全空: 自动打自己(空弹无伤), 保留回合
for (i = t.pos; i < t.nShells; i++) t.shells[i] = 0;
t.playerTurn = 1;
int hpBefore = t.hp[0];
int rc = playerAction(t);
if (rc != 0 || t.hp[0] != hpBefore) { printf("全空自动执行异常\n"); ok = 0; }
if (t.playerTurn != 1) { printf("全空自动打自己后应保留回合\n"); ok = 0; }
printf("全空自动执行: %s (自动打自己保留回合)\n", ok ? "OK" : "FAIL");
// 全实: 自动打对方, 移交回合
newRound(t);
for (i = t.pos; i < t.nShells; i++) t.shells[i] = 1;
t.playerTurn = 1;
int dhp = t.hp[1];
rc = playerAction(t);
if (rc != 0 || t.hp[1] != dhp - 1) { printf("全实自动执行异常\n"); ok = 0; }
if (t.playerTurn != 0) { printf("全实自动打对方后应移交回合\n"); ok = 0; }
printf("全实自动执行: %s (自动打对方造成1伤)\n", ok ? "OK" : "FAIL");
g_auto = oldAuto; g_noPause = oldPause; g_autoCertain = oldCert;
}
// --- 手铐: 铐住恶魔后射击空弹, 恶魔被铐跳过时反馈不得残留"你射击..." ---
{
Game t;
resetGame(t, MODE_CLASSIC, DIFF_EASY, "ItemTest");
newRound(t);
int oldPause = g_noPause; g_noPause = 1;
t.cuffed[1] = 1; // 恶魔已被手铐铐住
t.playerTurn = 1; // 玩家回合
int i;
for (i = t.pos; i < t.nShells; i++) t.shells[i] = 0; // 保证下一发是空弹
fire(t, 1); // 玩家射击恶魔: 空弹, 行动权移交恶魔
if (t.playerTurn != 0) { printf("射击对方后应移交恶魔回合\n"); ok = 0; }
dealerAction(t); // 恶魔被铐, 回合跳过
if (t.playerTurn != 1) { printf("恶魔被铐跳过应把回合还给玩家\n"); ok = 0; }
if (strstr(g_lastMsg, "手铐") == NULL || strstr(g_lastMsg, "你射击") != NULL) {
printf("手铐跳过反馈错误(残留上一步文案): %s\n", g_lastMsg); ok = 0;
}
printf("手铐跳过反馈: %s (%s)\n", ok ? "OK" : "FAIL", g_lastMsg);
g_noPause = oldPause;
}
printf(ok ? "\nItemTest 全部通过\n" : "\nItemTest 存在失败项\n");
}
int main(int argc, char** argv) {
// 编码说明: 源码为 UTF-8(GCC10 要求)。请在编译选项中添加 -fexec-charset=GBK,
// 使 exe 内中文字符串以 GBK 存储, 与 Windows 控制台的 GBK 渲染一致, 中文即可正常显示。
srand((unsigned)time(NULL));
ensureDirs();
if (argc > 1 && strcmp(argv[1], "autotest") == 0) {
runAutoTest();
return 0;
}
if (argc > 1 && strcmp(argv[1], "flowtest") == 0) {
runFlowTest();
return 0;
}
if (argc > 1 && strcmp(argv[1], "itemtest") == 0) {
runItemTest();
return 0;
}
mainMenu();
return 0;
}