// ============================================================
//  恶魔轮盘 (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;          // 受伤 -&gt; 行动权转移
        } 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("    - 射击自己: 实弹-&gt;自己掉血, 行动权移交; 空弹-&gt;毫发无损, 保留行动权(赚回合)\n");
    printf("    - 射击对方: 实弹-&gt;对方掉血, 行动权移交对方; 空弹-&gt;白送对方回合\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("   逆转器: 将枪膛内剩余所有子弹翻转(实弹&lt;-&gt;空弹)\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;
}