🎮ArcadeLab

税务稽查风云录 · 角色扮演

by SparkGlider58
1167 lines73.4 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>税务稽查风云录 · 角色扮演</title>
    <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">
    <style>
        /* ===== 像素风格 ===== */
        * { box-sizing: border-box; margin: 0; padding: 0; image-rendering: pixelated; }
        body {
            background: #1a1a2e;
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Press Start 2P', monospace;
            padding: 16px;
            margin: 0;
        }
        #game-container {
            max-width: 820px;
            width: 100%;
            background: #16213e;
            border: 8px solid #0f3460;
            box-shadow: 0 0 0 4px #533483, 0 0 0 8px #0f3460, 0 8px 0 8px #0a0a1a;
            padding: 20px 18px 24px;
            border-radius: 4px;
            color: #eaeaea;
            position: relative;
        }
        .pixel-divider {
            width: 100%;
            height: 4px;
            background: repeating-linear-gradient(90deg, #f3c623 0px, #f3c623 8px, #533483 8px, #533483 16px);
            margin: 12px 0 16px;
        }
        #status-bar {
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            background: #0f3460;
            padding: 10px 14px;
            border: 4px solid #533483;
            font-size: 12px;
            gap: 8px;
        }
        #status-bar span {
            background: #1a1a2e;
            padding: 5px 10px;
            border: 2px solid #3a1c5a;
            color: #f3c623;
            text-shadow: 2px 2px 0 #0a0a1a;
        }
        .panel {
            background: #1a1a2e;
            border: 6px solid #0f3460;
            padding: 20px 18px;
            margin: 12px 0 10px;
            box-shadow: inset 0 0 0 3px #2a1a4a;
            min-height: 280px;
        }
        .pixel-btn {
            background: #f3c623;
            border: none;
            border-bottom: 8px solid #b38f1a;
            color: #0f0f1f;
            font-family: 'Press Start 2P', monospace;
            font-size: 16px;
            padding: 12px 24px;
            cursor: pointer;
            text-shadow: 2px 2px 0 #d4ad2a;
            transition: all 0.05s linear;
            box-shadow: 0 4px 0 #2a1a3a;
            margin: 4px 2px;
        }
        .pixel-btn:active { transform: translateY(6px); border-bottom-width: 2px; }
        .pixel-btn:disabled { opacity: 0.5; transform: translateY(4px); border-bottom-width: 4px; pointer-events: none; }
        .pixel-btn.secondary { background: #5a4a7a; border-bottom-color: #2a1a4a; color: #f0eef5; text-shadow: 2px 2px 0 #2a1a4a; }
        .btn-group { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin: 16px 0 6px; }
        .story-container {
            background: #0f1a2f;
            border: 4px solid #3a2a5a;
            padding: 16px;
            margin-bottom: 14px;
            line-height: 2;
            font-size: 14px;
            white-space: pre-wrap;
            word-break: break-word;
        }
        .story-speaker { color: #f3c623; font-weight: bold; }
        .story-dialogue { color: #d4eaf0; }
        .story-scene {
            color: #b0b0d0;
            font-style: italic;
            font-size: 12px;
            margin-bottom: 8px;
        }
        .pixel-avatar {
            display: inline-block;
            width: 40px;
            height: 40px;
            background: #3a2a5a;
            border: 3px solid #6a4a8a;
            margin-right: 10px;
            vertical-align: middle;
            text-align: center;
            line-height: 40px;
            font-size: 20px;
            color: #f3c623;
        }
        .option-grid {
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin: 12px 0;
        }
        .option-btn {
            background: #25254a;
            border: 4px solid #3a2a5a;
            color: #eaeaea;
            font-family: 'Press Start 2P', monospace;
            font-size: 13px;
            padding: 12px 16px;
            text-align: left;
            cursor: pointer;
            transition: all 0.05s;
            box-shadow: 0 4px 0 #0a0a1a;
            line-height: 1.6;
        }
        .option-btn:hover:not(:disabled) { background: #2f2f5a; border-color: #6a4a8a; }
        .option-btn:active:not(:disabled) { transform: translateY(4px); box-shadow: 0 0px 0 #0a0a1a; }
        .option-btn.correct { background: #2a7a3a; border-color: #5ab86a; color: #d4f0d4; }
        .option-btn.wrong { background: #7a2a2a; border-color: #c44a4a; color: #f0d4d4; }
        .option-btn:disabled { opacity: 0.8; cursor: not-allowed; }
        .feedback-box {
            background: #0f1a2f;
            border: 4px solid #3a2a5a;
            padding: 14px;
            margin: 10px 0;
            font-size: 13px;
            line-height: 1.8;
            color: #d4d4e8;
            white-space: pre-wrap;
        }
        .ending-title { font-size: 26px; text-align: center; padding: 16px 0; }
        .ending-gold { color: #f3c623; text-shadow: 4px 4px 0 #533483; }
        .ending-fire { color: #f06060; text-shadow: 4px 4px 0 #6a1a1a; }
        .ending-silver { color: #b0b0d0; text-shadow: 4px 4px 0 #3a3a5a; }
        .honor-badge {
            display: inline-block;
            background: #f3c623;
            color: #0f0f1f;
            padding: 4px 8px;
            border: 3px solid #b38f1a;
            font-size: 10px;
            margin: 2px;
        }
        @media (max-width: 600px) {
            #game-container { padding: 12px 10px 16px; }
            .pixel-btn { font-size: 13px; padding: 10px 14px; }
            .option-btn { font-size: 11px; padding: 8px 10px; }
            #status-bar { font-size: 10px; }
            #status-bar span { padding: 4px 6px; }
        }
    </style>
</head>
<body>
<div id="game-container">
    <div id="app"></div>
</div>
<script>
    (function() {
        "use strict";

        // ================================================================
        //  1. 案 件 剧 情 与 题 目 (检查过程导向,故事化)
        //     每个案例包含story(含场景)和questions(至少20题)
        // ================================================================

        function genId(q) { return q.q.slice(0, 30) + q.opts.join('|'); }

        const CASE_STORIES = [
            // ---- 案例1:明星逃税案 ----
            {
                title: '明星逃税案 · 检查过程',
                story: [
                    { type: 'scene', text: '📍 税务局稽查科办公室,早上8:30' },
                    { type: 'narrator', text: '你接到线索:某明星通过工作室虚构业务,将个人收入转为经营所得,少缴税款。' },
                    { type: 'dialogue', speaker: '股长', text: '小铁,这个案子你负责。先去调取工作室的工商登记和纳税申报资料,然后我们讨论检查方向。' },
                    { type: 'dialogue', speaker: '小铁', text: '明白,股长!我马上去办。' },
                ],
                questions: [
                    { q: '你来到工作室,财务人员很紧张。你首先应该要求查看什么?', opts: ['合同和业务真实性证明', '银行流水', '成本费用发票', '员工工资表'], ans: 0, explain: '核查业务真实性是第一步,合同和业务证明是核心。', difficulty: 'easy' },
                    { q: '你发现工作室有几笔大额“咨询费”支出,但没有具体成果报告。下一步你应该?', opts: ['调取咨询公司信息', '要求提供咨询成果', '直接认定为虚列成本', '询问财务人员'], ans: 0, explain: '调取对方公司信息,核实是否真实发生业务。', difficulty: 'easy' },
                    { q: '明星声称收入全部用于公益,但无法提供捐赠票据。你应该?', opts: ['去民政部门核实', '要求补票据', '直接否定扣除', '相信明星'], ans: 0, explain: '去民政部门核实捐赠真实性。', difficulty: 'easy' },
                    { q: '你发现工作室与一家空壳公司有资金往来,但无实际业务。下一步检查方向是?', opts: ['核查空壳公司注册信息', '询问明星本人', '检查空壳公司纳税申报', '以上都是'], ans: 3, explain: '综合核查空壳公司。', difficulty: 'easy' },
                    { q: '你决定调取工作室的银行账户流水,应该以什么名义?', opts: ['税务稽查通知书', '协助调查函', '直接去银行', '电话通知'], ans: 0, explain: '依法必须使用稽查通知书,凭合法手续调取。', difficulty: 'easy' },
                    { q: '工作室会计说很多凭证丢失了,你如何应对?', opts: ['调取税务机关备份数据', '要求限期补正', '根据现有资料核定', '通知公安介入'], ans: 0, explain: '税务机关有数据备份,可调取电子数据。', difficulty: 'medium' },
                    { q: '你发现明星个人账户频繁收到大额转账,疑似隐匿收入。你该怎么做?', opts: ['申请查询个人账户', '直接冻结账户', '向领导汇报', '约谈明星'], ans: 0, explain: '需依法申请查询个人账户,不可私自冻结。', difficulty: 'medium' },
                    { q: '在检查过程中,明星的助理突然要求见你,想私下沟通,你如何应对?', opts: ['报告领导后决定', '拒绝,坚持按程序', '同意,看对方说什么', '录音后同意'], ans: 0, explain: '应报告领导,按程序处理,避免私下接触。', difficulty: 'easy' },
                    { q: '你发现工作室有大量“劳务费”列支,但实际没有相应的劳务合同,你怀疑虚列。下一步?', opts: ['向劳务提供方发函核实', '要求提供劳务合同', '直接调整应纳税所得额', '约谈负责人'], ans: 0, explain: '向劳务提供方发函核实是常见取证手段。', difficulty: 'medium' },
                    { q: '检查接近尾声,你准备制作检查工作底稿,应重点记录什么?', opts: ['检查过程和方法', '发现的问题和证据', '结论和处理建议', '以上都是'], ans: 3, explain: '底稿应全面记录。', difficulty: 'easy' },
                    { q: '你怀疑明星利用境外账户转移收入,但无权直接查境外账户,应如何获取线索?', opts: ['通过国际税收协定请求协助', '放弃调查', '直接发函境外银行', '约谈明星施压'], ans: 0, explain: '国际税收协定是合法渠道。', difficulty: 'medium' },
                    { q: '检查中发现工作室有一批发票来自已注销企业,你的处理方式是?', opts: ['认定为虚开发票', '去工商部门核实注销信息', '要求对方重新开具', '忽略'], ans: 0, explain: '已注销企业开具的发票无效,应认定为虚开。', difficulty: 'easy' },
                    { q: '工作室财务软件中的数据你怀疑被篡改,应如何获取原始数据?', opts: ['联系软件服务商', '使用专业数据恢复工具', '扣押电脑送检', '以上都是'], ans: 3, explain: '综合方法获取原始数据。', difficulty: 'medium' },
                    { q: '你发现明星的律师试图干扰检查,你应如何应对?', opts: ['记录并报告领导', '警告律师', '请律师回避', '与律师直接对抗'], ans: 0, explain: '应记录干扰行为并向上级报告。', difficulty: 'easy' },
                    { q: '检查中,你发现少缴税款金额较大,可能涉嫌犯罪,你应启动什么程序?', opts: ['移送公安', '直接处罚', '补缴税款了事', '召开听证会'], ans: 0, explain: '达到立案标准应移送公安。', difficulty: 'easy' },
                    { q: '你决定对明星进行约谈,应提前准备什么?', opts: ['问题清单和证据', '法律条文', '询问提纲', '以上都是'], ans: 3, explain: '充分准备。', difficulty: 'easy' },
                    { q: '工作室提出证据证明部分支出真实,你应如何对待?', opts: ['实地核实', '一概采信', '要求公证', '直接排除'], ans: 0, explain: '应进行实地核实。', difficulty: 'easy' },
                    { q: '检查结束,你撰写调查报告,应包含哪些要素?', opts: ['检查过程、问题、证据、处理建议', '仅问题', '仅结论', '简单说明'], ans: 0, explain: '全面报告。', difficulty: 'easy' },
                    { q: '你发现工作室有涉嫌骗税行为,应重点收集哪类证据?', opts: ['虚假合同', '伪造报关单', '资金回流', '以上都是'], ans: 3, explain: '骗税需收集多种证据。', difficulty: 'medium' },
                    { q: '最后,你向股长汇报时,应突出什么?', opts: ['检查方法和过程', '纳税人的态度', '你的个人感受', '无关细节'], ans: 0, explain: '突出检查过程和发现的问题。', difficulty: 'easy' },
                ]
            },
            // ---- 案例2:加油站隐匿收入案 ----
            {
                title: '加油站隐匿收入案',
                story: [
                    { type: 'scene', text: '📍 税务局稽查科,下午2:00' },
                    { type: 'narrator', text: '某加油站被举报利用个人微信收款,隐匿销售收入。' },
                    { type: 'dialogue', speaker: '股长', text: '小铁,你带人去现场核查,重点检查收银系统和银行流水。' },
                    { type: 'dialogue', speaker: '小铁', text: '好的,股长!我马上出发。' },
                ],
                questions: [
                    { q: '到达加油站,你应该首先检查什么?', opts: ['收银机系统', '油罐库存', '员工考勤', '税务登记证'], ans: 0, explain: '收银系统记录销售额,是核心。', difficulty: 'easy' },
                    { q: '你发现收银系统有“删除记录”功能,应如何应对?', opts: ['检查是否有删除痕迹', '要求恢复删除数据', '记录该功能', '以上都是'], ans: 3, explain: '全面检查。', difficulty: 'easy' },
                    { q: '老板说很多顾客用现金,未在系统记录,你该如何核实?', opts: ['比对油品采购和销售量', '清点现金库存', '调查员工', '查看监控'], ans: 0, explain: '通过购销存数据推算隐匿收入。', difficulty: 'medium' },
                    { q: '你发现老板个人微信有大量收款记录,但未入账,下一步?', opts: ['复制微信交易记录', '向腾讯公司调取数据', '询问老板', '以上都是'], ans: 3, explain: '综合取证。', difficulty: 'medium' },
                    { q: '加油站有多个加油枪,你如何核查每把枪的实际销售?', opts: ['读取加油机总累计数', '比对加油机读数与收银记录', '检查加油机铅封', '以上都是'], ans: 3, explain: '多角度核查。', difficulty: 'medium' },
                    { q: '你怀疑加油站有“跳枪”作弊行为,应检查什么?', opts: ['加油机计量器', '是否有外挂装置', '油枪出油速度', '以上都是'], ans: 3, explain: '全面检查作弊手段。', difficulty: 'medium' },
                    { q: '老板无法提供完整的进货发票,你如何确定进货成本?', opts: ['去上游供应商调取数据', '按行业平均成本核定', '实地盘点油品', '估算'], ans: 0, explain: '向上游核实更准确。', difficulty: 'easy' },
                    { q: '检查中,发现油罐库存与销售记录严重不符,你如何分析?', opts: ['可能存在账外销售', '可能存在偷油', '可能库存盘点误差', '以上都是'], ans: 3, explain: '多种可能,需综合分析。', difficulty: 'easy' },
                    { q: '你决定调取加油站的银行账户流水,应准备什么文书?', opts: ['协助查询账户通知书', '税务检查通知书', '调账通知', '行政处罚告知书'], ans: 0, explain: '查询账户需用法定文书。', difficulty: 'easy' },
                    { q: '案件查结,你发现少缴税款数额较大,应如何处理?', opts: ['追缴税款', '加收滞纳金', '罚款', '以上都是'], ans: 3, explain: '依法全面处理。', difficulty: 'easy' },
                    { q: '你发现加油站有员工私自收款,应如何定性?', opts: ['企业偷税', '员工个人行为', '管理漏洞', '一般违规'], ans: 0, explain: '员工行为代表企业,需企业负责。', difficulty: 'easy' },
                    { q: '你计划对加油站进行突击检查,应选择什么时间?', opts: ['上班高峰期', '夜间', '随机', '老板不在时'], ans: 0, explain: '高峰期能获取更多销售数据。', difficulty: 'easy' },
                    { q: '检查中,老板拒绝提供收银系统权限,你应如何?', opts: ['警告并限期提供', '强行破解', '申请法院强制', '停止检查'], ans: 0, explain: '依法要求提供,否则可处罚。', difficulty: 'easy' },
                    { q: '你通过比对油品进销存,发现隐匿收入约200万元,下一步?', opts: ['制作工作底稿', '补税', '立案', '以上都是'], ans: 3, explain: '综合处理。', difficulty: 'easy' },
                    { q: '加油站有多本账簿,你应如何核对?', opts: ['全面核查', '选择真实账簿', '要求外账', '抽查'], ans: 0, explain: '全面核查才能发现问题。', difficulty: 'easy' },
                    { q: '你发现加油站有挂靠经营,实际经营者不是登记人,应如何确定纳税人?', opts: ['以登记人为纳税人', '以实际经营者为纳税人', '两者都是', '不确定'], ans: 0, explain: '税法以登记人为纳税人。', difficulty: 'medium' },
                    { q: '检查结束,你应如何保管证据?', opts: ['装订成卷', '拍照存档', '电子备份', '以上都是'], ans: 3, explain: '确保证据安全。', difficulty: 'easy' },
                    { q: '你向股长汇报时,应重点说明什么?', opts: ['隐匿收入事实', '检查过程', '证据链条', '以上都是'], ans: 3, explain: '全面汇报。', difficulty: 'easy' },
                    { q: '如果加油站对处罚不服,你应告知其什么权利?', opts: ['申请复议', '提起诉讼', '申诉', '以上都是'], ans: 3, explain: '告知救济权利。', difficulty: 'easy' },
                    { q: '本案给稽查干部的启示是?', opts: ['检查要细致', '要善于利用数据分析', '注重证据链', '以上都是'], ans: 3, explain: '综合启示。', difficulty: 'easy' },
                ]
            },
            // ---- 案例3:网络主播偷税案 ----
            {
                title: '网络主播偷税案',
                story: [
                    { type: 'scene', text: '📍 税务局会议室,上午9:00' },
                    { type: 'narrator', text: '某网络主播被举报通过虚构业务将劳务报酬转为经营所得。' },
                    { type: 'dialogue', speaker: '股长', text: '小铁,你去查一下她的收入来源和成本费用结构。' },
                    { type: 'dialogue', speaker: '小铁', text: '好的,股长!我马上去了解。' },
                ],
                questions: [
                    { q: '你首先应该获取主播的什么信息?', opts: ['直播平台流水', '银行账户', '税务申报记录', '以上都是'], ans: 3, explain: '全面获取。', difficulty: 'easy' },
                    { q: '你发现主播成立了一家个人工作室,并将直播收入全部转入工作室,应如何定性?', opts: ['偷税', '合法避税', '合理节税', '漏税'], ans: 0, explain: '如果工作室无实际运营,则属于偷税。', difficulty: 'easy' },
                    { q: '你决定核查工作室的成本费用,重点应检查什么?', opts: ['人员工资', '办公费用', '是否有真实业务', '以上都是'], ans: 3, explain: '全面核查。', difficulty: 'easy' },
                    { q: '主播声称工作室有大量“推广费”,但无法提供合同,你应如何取证?', opts: ['向推广方发函', '要求补合同', '直接剔除', '询问主播'], ans: 0, explain: '发函核实。', difficulty: 'medium' },
                    { q: '你调取主播银行流水,发现有大额个人消费支出,但未在工作室列支,你如何分析?', opts: ['需要进一步调查', '可能与工作室无关', '可能隐匿收入', '放弃'], ans: 0, explain: '需进一步调查。', difficulty: 'easy' },
                    { q: '直播平台提供的数据显示主播实际收入远高于申报数,下一步?', opts: ['约谈主播', '直接补税', '调取平台与主播的合同', '以上都是'], ans: 3, explain: '综合处理。', difficulty: 'easy' },
                    { q: '你怀疑主播通过“刷榜”虚构业务,应如何查证?', opts: ['分析平台数据', '查资金流向', '询问平台工作人员', '以上都是'], ans: 3, explain: '多角度查证。', difficulty: 'medium' },
                    { q: '主播的会计说部分凭证丢失,你应如何?', opts: ['要求提供电子备份', '去税务局调取备份', '警告', '处罚'], ans: 0, explain: '要求提供电子数据。', difficulty: 'easy' },
                    { q: '检查中,主播态度恶劣,拒绝配合,你应如何?', opts: ['记录并报告', '强制检查', '请公安机关协助', '以上都是'], ans: 3, explain: '依法应对。', difficulty: 'easy' },
                    { q: '案件查结,你发现少缴个税金额巨大,应建议移送什么机关?', opts: ['公安机关', '检察院', '法院', '监察委'], ans: 0, explain: '移送公安追究刑事责任。', difficulty: 'easy' },
                    { q: '你准备对主播进行询问,应提前准备什么?', opts: ['询问提纲', '证据复印件', '法律条文', '以上都是'], ans: 3, explain: '充分准备。', difficulty: 'easy' },
                    { q: '主播提出新证据,你应如何处理?', opts: ['核实真伪', '采信', '忽略', '交领导'], ans: 0, explain: '先核实。', difficulty: 'easy' },
                    { q: '你发现主播有境外收入,如何获取线索?', opts: ['通过平台数据', '银行跨境流水', '国际情报交换', '以上都是'], ans: 3, explain: '综合途径。', difficulty: 'medium' },
                    { q: '主播的工作室注册地址是虚拟地址,你应如何?', opts: ['去工商核实', '实地核查', '认定为虚假', '忽略'], ans: 0, explain: '核实注册信息。', difficulty: 'easy' },
                    { q: '你决定采用电子取证,应如何确保数据完整性?', opts: ['制作镜像', '计算哈希值', '现场封存', '以上都是'], ans: 3, explain: '确保数据不被篡改。', difficulty: 'medium' },
                    { q: '主播提出行政复议,你应准备什么?', opts: ['答辩书', '证据材料', '法律依据', '以上都是'], ans: 3, explain: '全面准备。', difficulty: 'easy' },
                    { q: '本案中,你学到的最重要稽查技巧是?', opts: ['大数据分析', '资金流追踪', '约谈技巧', '以上都是'], ans: 3, explain: '综合提升。', difficulty: 'easy' },
                    { q: '你发现主播与平台有特殊分成协议,应如何获取?', opts: ['要求平台提供', '调取合同', '询问主播', '以上都是'], ans: 3, explain: '多渠道获取。', difficulty: 'easy' },
                    { q: '案件结束后,你应如何归档?', opts: ['整理成卷', '电子备份', '移交档案室', '以上都是'], ans: 3, explain: '规范归档。', difficulty: 'easy' },
                    { q: '股长问:本案的关键突破口是什么?你回答?', opts: ['银行流水', '平台数据', '虚假合同', '以上都是'], ans: 3, explain: '综合突破。', difficulty: 'easy' },
                ]
            },
            // ---- 案例4:外贸企业骗税案 ----
            {
                title: '外贸企业骗税案',
                story: [
                    { type: 'scene', text: '📍 外贸企业会议室,下午3:00' },
                    { type: 'narrator', text: '某外贸企业被怀疑通过假报出口骗取出口退税。' },
                    { type: 'dialogue', speaker: '股长', text: '小铁,你去核查报关单、合同和资金流是否一致。' },
                    { type: 'dialogue', speaker: '小铁', text: '好的,股长!我马上调取资料。' },
                ],
                questions: [
                    { q: '你到达企业,首先应调取什么资料?', opts: ['出口报关单', '购销合同', '外汇收汇记录', '以上都是'], ans: 3, explain: '全面调取。', difficulty: 'easy' },
                    { q: '你发现报关单上的货物与合同不符,下一步?', opts: ['向海关核实', '认定为骗税', '询问业务员', '约谈老板'], ans: 0, explain: '向海关核实数据。', difficulty: 'easy' },
                    { q: '企业无法提供出口货物运输单据,你应如何?', opts: ['调取船公司记录', '认定为假出口', '放弃核查', '要求补单'], ans: 0, explain: '调取船运记录。', difficulty: 'medium' },
                    { q: '你发现企业收汇金额小于报关金额,这可能说明什么?', opts: ['高报出口', '骗税', '资金不足', '正常差异'], ans: 0, explain: '高报出口骗取退税。', difficulty: 'medium' },
                    { q: '检查中,企业会计称凭证被烧毁,你应如何?', opts: ['去税务局调取', '要求提供复印件', '认定偷税', '请公安介入'], ans: 0, explain: '税务机关有数据备份。', difficulty: 'easy' },
                    { q: '你怀疑企业利用“借壳出口”,应查什么?', opts: ['实际货主', '中间商', '资金链条', '以上都是'], ans: 3, explain: '全面排查。', difficulty: 'medium' },
                    { q: '企业老板说货物是自己生产的,但厂区已停产,你应如何?', opts: ['核实生产能力', '调查生产记录', '询问工人', '以上都是'], ans: 3, explain: '综合核实。', difficulty: 'easy' },
                    { q: '你决定对企业的出口业务进行函调,应向哪个机构发函?', opts: ['海关', '外汇管理局', '税务局', '以上都是'], ans: 3, explain: '多部门函调。', difficulty: 'easy' },
                    { q: '检查中,企业提出新证据证明出口真实,你应如何?', opts: ['重新核实', '采信', '忽略', '交领导'], ans: 0, explain: '核实真伪。', difficulty: 'easy' },
                    { q: '案件查结,你发现骗税金额巨大,应建议移送什么机关?', opts: ['公安', '检察院', '法院', '监察委'], ans: 0, explain: '移送公安。', difficulty: 'easy' },
                    { q: '你发现企业有多个银行账户,资金分散,应如何追踪?', opts: ['全部调取流水', '分析资金流向', '寻找关联账户', '以上都是'], ans: 3, explain: '全面追踪。', difficulty: 'medium' },
                    { q: '企业说货物已出口但提单未拿到,你应如何?', opts: ['去船公司查询', '要求提供提单', '认定为未出口', '等待'], ans: 0, explain: '去船公司核实。', difficulty: 'easy' },
                    { q: '你决定采用实地核查,应关注什么?', opts: ['仓库货物', '生产设备', '工人数量', '以上都是'], ans: 3, explain: '全面观察。', difficulty: 'easy' },
                    { q: '企业被查后,立即注销税务登记,你应如何?', opts: ['追缴税款', '移送公安', '追溯股东责任', '以上都是'], ans: 3, explain: '依法追究。', difficulty: 'medium' },
                    { q: '你发现出口商品是禁止出口的,应如何?', opts: ['移交海关', '认定为骗税', '补税', '罚款'], ans: 0, explain: '涉嫌走私,移交海关。', difficulty: 'easy' },
                    { q: '本案的检查难点是什么?', opts: ['跨境取证', '资金链复杂', '企业不配合', '以上都是'], ans: 3, explain: '多重难点。', difficulty: 'easy' },
                    { q: '你准备约谈企业负责人,应如何准备?', opts: ['梳理证据', '拟定问题', '了解法律', '以上都是'], ans: 3, explain: '充分准备。', difficulty: 'easy' },
                    { q: '企业负责人态度强硬,拒绝签字,你应如何?', opts: ['注明情况', '录像', '请见证人', '以上都是'], ans: 3, explain: '依法应对。', difficulty: 'easy' },
                    { q: '案件结束后,你应总结什么?', opts: ['检查经验', '不足', '改进措施', '以上都是'], ans: 3, explain: '全面总结。', difficulty: 'easy' },
                    { q: '股长评价:本案成功的关键是?', opts: ['数据分析', '部门协作', '细致检查', '以上都是'], ans: 3, explain: '综合因素。', difficulty: 'easy' },
                ]
            },
            // ---- 案例5:房地产企业偷税案 ----
            {
                title: '房地产企业偷税案',
                story: [
                    { type: 'scene', text: '📍 房地产企业财务部,上午10:00' },
                    { type: 'narrator', text: '某房地产企业将预收房款挂账,不确认收入,少缴企业所得税。' },
                    { type: 'dialogue', speaker: '股长', text: '小铁,你去查他们的预售合同和银行回款,重点核实收入确认时点。' },
                    { type: 'dialogue', speaker: '小铁', text: '明白,股长!我马上核对台账。' },
                ],
                questions: [
                    { q: '你首先应获取什么资料?', opts: ['预售许可证', '销售合同台账', '银行按揭回款记录', '以上都是'], ans: 3, explain: '全面获取。', difficulty: 'easy' },
                    { q: '你发现企业预收房款长期挂账,未结转收入,应如何?', opts: ['认定为偷税', '要求补计收入', '加收滞纳金', '以上都是'], ans: 3, explain: '依法处理。', difficulty: 'easy' },
                    { q: '企业说房子未交付,所以不确认收入,你如何反驳?', opts: ['按税务规定确认', '按合同约定确认', '按交付时间确认', '以上都不是'], ans: 0, explain: '税务有专门规定。', difficulty: 'medium' },
                    { q: '你决定对企业的开发成本进行核查,应重点关注什么?', opts: ['建安成本', '配套设施费', '前期工程费', '以上都是'], ans: 3, explain: '全面核查。', difficulty: 'medium' },
                    { q: '企业提供了一堆发票,但金额与合同不符,你应如何?', opts: ['核实真实性', '认定为虚列', '要求重开', '忽略'], ans: 0, explain: '核实。', difficulty: 'easy' },
                    { q: '你发现企业有大量“业务招待费”列支,应如何?', opts: ['检查是否真实', '按比例扣除', '全部剔除', '询问'], ans: 0, explain: '核实真实性。', difficulty: 'easy' },
                    { q: '企业聘请的税务顾问提出异议,你应如何?', opts: ['听取并核实', '忽略', '要求书面意见', '以上都是'], ans: 3, explain: '认真对待。', difficulty: 'easy' },
                    { q: '你发现企业将个人消费列入开发成本,应如何?', opts: ['调整', '罚款', '补税', '以上都是'], ans: 3, explain: '依法处理。', difficulty: 'easy' },
                    { q: '案件查结,你发现少缴税款数额巨大,应建议移送?', opts: ['公安', '检察院', '法院', '监察委'], ans: 0, explain: '移送公安。', difficulty: 'easy' },
                    { q: '股长问:本案关键突破口是什么?你回答?', opts: ['预售合同', '银行流水', '成本发票', '以上都是'], ans: 3, explain: '综合突破。', difficulty: 'easy' },
                    { q: '你如何核查预收房款的真实金额?', opts: ['比对银行回款', '查销售合同', '网签备案', '以上都是'], ans: 3, explain: '多渠道核实。', difficulty: 'easy' },
                    { q: '企业说部分房款未收到,你应如何?', opts: ['核查应收账', '函证', '约谈', '以上都是'], ans: 3, explain: '综合核实。', difficulty: 'easy' },
                    { q: '你发现企业有“阴阳合同”,应如何?', opts: ['以真实合同为准', '处罚', '补税', '以上都是'], ans: 3, explain: '依法处理。', difficulty: 'medium' },
                    { q: '检查中,企业财务人员更换频繁,你应如何?', opts: ['询问历任会计', '查账', '查找原始凭证', '以上都是'], ans: 3, explain: '全面调查。', difficulty: 'easy' },
                    { q: '你决定对项目进行实地查勘,应关注什么?', opts: ['工程进度', '实际销售', '入住情况', '以上都是'], ans: 3, explain: '全面观察。', difficulty: 'easy' },
                    { q: '企业以商业秘密为由拒绝提供资料,你应如何?', opts: ['依法强制', '处罚', '报告', '以上都是'], ans: 3, explain: '依法应对。', difficulty: 'easy' },
                    { q: '你发现企业有大量“咨询费”列支,但无具体项目,应如何?', opts: ['核实', '剔除', '要求提供合同', '以上都是'], ans: 3, explain: '综合处理。', difficulty: 'easy' },
                    { q: '案件处理结束,你应总结什么经验?', opts: ['检查技巧', '法律适用', '取证方法', '以上都是'], ans: 3, explain: '全面总结。', difficulty: 'easy' },
                    { q: '股长表扬你,理由是?', opts: ['检查细致', '证据充分', '定性准确', '以上都是'], ans: 3, explain: '全面肯定。', difficulty: 'easy' },
                    { q: '本案给你的最大启示是?', opts: ['重视合同', '注重现金流', '关注成本真实性', '以上都是'], ans: 3, explain: '综合启示。', difficulty: 'easy' },
                ]
            },
            // ---- 案例6:餐饮企业隐匿收入案 ----
            {
                title: '餐饮企业隐匿收入案',
                story: [
                    { type: 'scene', text: '📍 餐厅门口,晚上7:00(突击检查)' },
                    { type: 'narrator', text: '某餐饮企业被举报通过个人微信收款隐匿收入。' },
                    { type: 'dialogue', speaker: '股长', text: '小铁,你去突击检查收银系统,并调取微信支付记录。' },
                    { type: 'dialogue', speaker: '小铁', text: '好的,股长!我马上行动。' },
                ],
                questions: [
                    { q: '你到达餐厅,首先应检查什么?', opts: ['收银机', '点菜系统', '库存', '员工考勤'], ans: 0, explain: '收银机记录销售额。', difficulty: 'easy' },
                    { q: '你发现收银系统有“反结账”功能,频繁使用,这说明什么?', opts: ['可能存在退单', '可能篡改收入', '正常操作', '需要进一步调查'], ans: 1, explain: '频繁反结账可能是篡改收入。', difficulty: 'medium' },
                    { q: '老板说很多顾客用现金,未入账,你如何核查?', opts: ['比对采购和销售', '询问员工', '查看监控', '以上都是'], ans: 3, explain: '综合核查。', difficulty: 'easy' },
                    { q: '你调取了老板个人微信收款记录,发现大量餐饮收入,下一步?', opts: ['认定为偷税', '要求补税', '加收滞纳金', '以上都是'], ans: 3, explain: '依法处理。', difficulty: 'easy' },
                    { q: '企业无法提供完整进货发票,你如何确定成本?', opts: ['核定', '比同行业', '实地盘点', '以上都是'], ans: 3, explain: '多种方法。', difficulty: 'easy' },
                    { q: '你发现餐厅有多个收款码,部分未报税,应如何?', opts: ['全部调取', '要求解释', '补税', '以上都是'], ans: 3, explain: '全面处理。', difficulty: 'easy' },
                    { q: '你决定对餐厅进行暗访,应怎么做?', opts: ['以顾客身份', '观察收银', '记录消费', '以上都是'], ans: 3, explain: '全面暗访。', difficulty: 'medium' },
                    { q: '企业提出部分收入是员工聚餐,不应纳税,你如何?', opts: ['核实', '认定为收入', '要求证明', '以上都是'], ans: 3, explain: '核实真实性。', difficulty: 'easy' },
                    { q: '你发现企业有“买赠”活动,但未视同销售,应如何?', opts: ['补税', '加收滞纳金', '罚款', '以上都是'], ans: 3, explain: '依法处理。', difficulty: 'medium' },
                    { q: '案件查结,你发现少缴税款,应如何处理?', opts: ['追缴', '加收滞纳金', '罚款', '以上都是'], ans: 3, explain: '全面处理。', difficulty: 'easy' },
                    { q: '你如何评估餐厅的真实营业额?', opts: ['能耗分析', '员工人数', '座位周转率', '以上都是'], ans: 3, explain: '多指标评估。', difficulty: 'medium' },
                    { q: '企业会计说账本丢失,你应如何?', opts: ['要求提供电子账', '去税务局调取', '处罚', '以上都是'], ans: 3, explain: '依法处理。', difficulty: 'easy' },
                    { q: '你发现餐厅有分店,但未合并申报,应如何?', opts: ['要求合并', '补税', '罚款', '以上都是'], ans: 3, explain: '依法处理。', difficulty: 'easy' },
                    { q: '你决定对餐厅的供应商进行函调,目的是什么?', opts: ['核实进货', '核实成本', '发现虚开', '以上都是'], ans: 3, explain: '多目的。', difficulty: 'easy' },
                    { q: '检查中,老板情绪激动,你应如何?', opts: ['安抚', '依法告知', '记录', '以上都是'], ans: 3, explain: '妥善应对。', difficulty: 'easy' },
                    { q: '你发现餐厅有“刷单”行为,虚增收入,但并未纳税,应如何?', opts: ['按实际收入征税', '罚款', '补税', '以上都是'], ans: 3, explain: '依法处理。', difficulty: 'medium' },
                    { q: '案件结束后,你总结的检查要点是?', opts: ['收入核对', '成本核查', '票据检查', '以上都是'], ans: 3, explain: '全面总结。', difficulty: 'easy' },
                    { q: '股长问:本案成功的关键是什么?', opts: ['突击检查', '数据比对', '多方取证', '以上都是'], ans: 3, explain: '综合因素。', difficulty: 'easy' },
                    { q: '你学习到的最重要稽查理念是?', opts: ['依法检查', '全面客观', '注重证据', '以上都是'], ans: 3, explain: '综合理念。', difficulty: 'easy' },
                    { q: '本案给税务干部的启示是?', opts: ['提高敏感度', '善于运用数据', '坚持原则', '以上都是'], ans: 3, explain: '综合启示。', difficulty: 'easy' },
                ]
            }
        ];

        // ================================================================
        //  2. 游 戏 核 心 状 态
        // ================================================================
        const LEVELS = [
            { name: '一级行政执法员', score: 0 },
            { name: '四级主办', score: 150 },
            { name: '副股长', score: 300 },
            { name: '股长', score: 500 },
            { name: '副局长', score: 800 },
            { name: '局长', score: 1200 }
        ];
        const PENALTY_NAMES = ['警告', '严重警告', '降级', '撤职', '开除'];

        let G = null;

        function defaultState() {
            return {
                year: 1,
                totalScore: 0,
                levelIndex: 0,
                penaltyCount: 0,
                currentCase: 0,
                currentQuestion: 0,
                caseResults: [],
                gameOver: false,
                endingType: null,
                storyStep: 0,
                inStory: false,
                caseIndex: 0,
                questionList: [],
                caseCorrectCount: 0,
                answered: false,
                feedbackMsg: '',
                integrityTriggered: false,
                integrityRejected: false,
                acceptedBribe: false,
                bribeAcceptedYear: 0,
                doubleCaseYear: 0,
                usedQuestionIds: [],
                casesPerYear: 5,
                honors: [],
                integrityStoryStep: 0,
                inIntegrityEvent: false,
                integrityChoiceMade: false,
            };
        }

        function getLevelName(idx) {
            return LEVELS[Math.min(idx, LEVELS.length - 1)].name;
        }
        function calcLevel(score) {
            let idx = 0;
            for (let i = LEVELS.length - 1; i >= 0; i--) {
                if (score >= LEVELS[i].score) { idx = i; break; }
            }
            return idx;
        }

        // ================================================================
        //  3. 状 态 存 储
        // ================================================================
        function saveState() {
            if (!G) return;
            const data = {
                year: G.year,
                totalScore: G.totalScore,
                levelIndex: G.levelIndex,
                penaltyCount: G.penaltyCount,
                currentCase: G.currentCase,
                currentQuestion: G.currentQuestion,
                caseResults: G.caseResults,
                gameOver: G.gameOver,
                endingType: G.endingType,
                integrityTriggered: G.integrityTriggered,
                integrityRejected: G.integrityRejected,
                acceptedBribe: G.acceptedBribe,
                bribeAcceptedYear: G.bribeAcceptedYear,
                doubleCaseYear: G.doubleCaseYear,
                usedQuestionIds: G.usedQuestionIds,
                casesPerYear: G.casesPerYear,
                honors: G.honors,
            };
            try { localStorage.setItem('taxGameState', JSON.stringify(data)); } catch (e) {}
        }

        function loadState() {
            try {
                const raw = localStorage.getItem('taxGameState');
                if (!raw) return null;
                const data = JSON.parse(raw);
                if (data.gameOver) return data;
                if (data.year === undefined) return null;
                return data;
            } catch (e) { return null; }
        }
        function clearState() { localStorage.removeItem('taxGameState'); }

        function restoreFromData(data) {
            G = defaultState();
            Object.assign(G, data);
            if (!G.honors) G.honors = [];
            if (G.gameOver) { renderEnding(); return; }
            renderMain();
        }

        // ================================================================
        //  4. 游 戏 逻 辑
        // ================================================================
        function initGame() {
            clearState();
            G = defaultState();
            G.honors = [];
            for (let y = 0; y < 10; y++) {
                G.caseResults[y] = [];
                for (let c = 0; c < 5; c++) {
                    G.caseResults[y][c] = -1;
                }
            }
            G.casesPerYear = 5;
            G.usedQuestionIds = [];
            renderMain();
        }

        function startYear() {
            if (!G || G.gameOver) return;
            if (G.year > 10) { retireGame(); return; }
            if (G.currentCase >= G.casesPerYear) {
                renderYearEnd();
                return;
            }
            startCaseStory();
        }

        function startCaseStory() {
            if (!G || G.gameOver) return;
            if (G.year > 10) { retireGame(); return; }
            if (G.currentCase >= G.casesPerYear) {
                G.currentCase = 0;
                G.year++;
                saveState();
                if (G.year > 10) { retireGame(); return; }
                renderYearEnd();
                return;
            }
            const caseIdx = Math.floor(Math.random() * CASE_STORIES.length);
            const storyData = CASE_STORIES[caseIdx];
            G.caseIndex = caseIdx;
            G.storyStep = 0;
            G.inStory = true;
            G.currentQuestion = 0;
            G.caseCorrectCount = 0;
            G.answered = false;
            G.feedbackMsg = '';
            const available = storyData.questions.filter(q => {
                const id = genId(q);
                return !G.usedQuestionIds.includes(id);
            });
            let pool = available;
            if (G.year <= 3) {
                pool = available.filter(q => q.difficulty === 'easy');
                if (pool.length < 5) pool = available;
            }
            if (pool.length < 5) {
                const all = storyData.questions;
                const extra = all.filter(q => !G.usedQuestionIds.includes(genId(q)));
                pool = pool.concat(extra);
                if (pool.length < 5) pool = all;
            }
            const shuffled = pool.sort(() => 0.5 - Math.random());
            const selected = shuffled.slice(0, 5);
            selected.forEach(q => {
                const id = genId(q);
                if (!G.usedQuestionIds.includes(id)) G.usedQuestionIds.push(id);
            });
            G.questionList = selected;
            saveState();
            renderStory();
        }

        function renderStory() {
            if (!G || !G.inStory) return;
            const storyData = CASE_STORIES[G.caseIndex];
            const story = storyData.story;
            if (G.storyStep >= story.length) {
                G.inStory = false;
                G.currentQuestion = 0;
                G.answered = false;
                saveState();
                renderQuestion();
                return;
            }
            const step = story[G.storyStep];
            let speaker = '', text = '', avatar = '', scene = '';
            if (step.type === 'scene') {
                scene = step.text;
            } else if (step.type === 'narrator') {
                speaker = '📖 叙述';
                avatar = '📜';
                text = step.text;
            } else if (step.type === 'dialogue') {
                speaker = step.speaker + ':';
                avatar = step.speaker === '小铁' ? '👤' : '👔';
                text = step.text;
            }
            const caseNum = G.currentCase + 1;
            const totalCases = G.casesPerYear;
            let sceneHtml = scene ? `<div class="story-scene">${scene}</div>` : '';
            render(`
                <div id="status-bar">
                    <span>📅 第 ${G.year}/10 年</span>
                    <span>⭐ ${G.totalScore} 分</span>
                    <span>🎖 ${getLevelName(G.levelIndex)}</span>
                    <span>📌 ${caseNum}/${totalCases} 案</span>
                    <span>🏅 ${G.honors.length}</span>
                </div>
                <div class="panel">
                    <div style="font-size:14px;color:#f3c623;margin-bottom:8px;">📜 案件:${storyData.title}</div>
                    <div class="story-container">
                        ${sceneHtml}
                        <div style="display:flex;align-items:flex-start;gap:8px;">
                            <div class="pixel-avatar">${avatar}</div>
                            <div>
                                <div class="story-speaker">${speaker}</div>
                                <div class="story-dialogue">${text}</div>
                            </div>
                        </div>
                    </div>
                    <div class="btn-group">
                        <button class="pixel-btn" onclick="window._nextStory()">📖 继续</button>
                    </div>
                </div>
            `);
        }

        window._nextStory = function() {
            if (!G || !G.inStory) return;
            G.storyStep++;
            saveState();
            renderStory();
        };

        function renderQuestion() {
            if (!G || G.gameOver) return;
            if (G.currentQuestion >= 5) {
                caseSettlement();
                return;
            }
            const q = G.questionList[G.currentQuestion];
            if (!q) { startCaseStory(); return; }
            const caseNum = G.currentCase + 1;
            const totalCases = G.casesPerYear;
            const idx = G.currentQuestion + 1;
            const storyData = CASE_STORIES[G.caseIndex];
            let optionsHtml = q.opts.map((opt, i) => `
                <button class="option-btn" data-idx="${i}" onclick="window._answerQuestion(${i})">
                    ${String.fromCharCode(65 + i)}. ${opt}
                </button>
            `).join('');

            render(`
                <div id="status-bar">
                    <span>📅 第 ${G.year}/10 年</span>
                    <span>⭐ ${G.totalScore} 分</span>
                    <span>🎖 ${getLevelName(G.levelIndex)}</span>
                    <span>📌 ${caseNum}/${totalCases} 案</span>
                    <span>🏅 ${G.honors.length}</span>
                </div>
                <div class="panel">
                    <div style="font-size:12px;color:#b0b0d0;margin-bottom:6px;">
                        ${storyData.title} · 第 ${idx}/5 题
                    </div>
                    <div style="font-size:16px;line-height:1.8;margin:10px 0 14px;">
                        ${q.q}
                    </div>
                    <div class="option-grid" id="optionGrid">
                        ${optionsHtml}
                    </div>
                    <div id="feedbackArea"></div>
                    <div class="btn-group" id="nextBtnGroup" style="display:none;">
                        <button class="pixel-btn" onclick="window._nextQuestion()">➡ 下一题</button>
                    </div>
                </div>
            `);
        }

        window._answerQuestion = function(selectedIdx) {
            if (!G || G.gameOver || G.answered) return;
            if (G.currentQuestion >= 5) return;
            const q = G.questionList[G.currentQuestion];
            if (!q) return;
            G.answered = true;
            const isCorrect = (selectedIdx === q.ans);
            if (isCorrect) {
                G.totalScore += 10;
                G.caseCorrectCount++;
            }
            const newLevel = calcLevel(G.totalScore);
            G.levelIndex = newLevel;
            saveState();

            let optionsHtml = q.opts.map((opt, i) => {
                let cls = 'option-btn';
                if (i === q.ans) cls += ' correct';
                else if (i === selectedIdx && !isCorrect) cls += ' wrong';
                return `<button class="${cls}" disabled>${String.fromCharCode(65 + i)}. ${opt}</button>`;
            }).join('');

            const storyData = CASE_STORIES[G.caseIndex];
            const caseNum = G.currentCase + 1;
            const totalCases = G.casesPerYear;
            const idx = G.currentQuestion + 1;
            const fb = isCorrect ? '✅ 回答正确!+10分' : '❌ 回答错误';
            const explain = q.explain || '';
            render(`
                <div id="status-bar">
                    <span>📅 第 ${G.year}/10 年</span>
                    <span>⭐ ${G.totalScore} 分</span>
                    <span>🎖 ${getLevelName(G.levelIndex)}</span>
                    <span>📌 ${caseNum}/${totalCases} 案</span>
                    <span>🏅 ${G.honors.length}</span>
                </div>
                <div class="panel">
                    <div style="font-size:12px;color:#b0b0d0;margin-bottom:6px;">
                        ${storyData.title} · 第 ${idx}/5 题
                    </div>
                    <div style="font-size:16px;line-height:1.8;margin:10px 0 14px;">
                        ${q.q}
                    </div>
                    <div class="option-grid">
                        ${optionsHtml}
                    </div>
                    <div class="feedback-box">${fb}\n${explain}</div>
                    <div class="btn-group">
                        <button class="pixel-btn" onclick="window._nextQuestion()">➡ 下一题</button>
                    </div>
                </div>
            `);
        };

        window._nextQuestion = function() {
            if (!G || G.gameOver) return;
            if (!G.answered) return;
            G.currentQuestion++;
            G.answered = false;
            saveState();
            if (G.currentQuestion >= 5) {
                caseSettlement();
                return;
            }
            renderQuestion();
        };

        // 案件结算 + 廉政事件
        function caseSettlement() {
            if (!G || G.gameOver) return;
            const correct = G.caseCorrectCount;
            let bonus = 0;
            let penaltyTrigger = false;
            let evalText = '';
            if (correct === 5) {
                bonus = 30;
                evalText = '🏆 完美';
                G.totalScore += bonus;
                if (!G.integrityTriggered && !G.integrityRejected) {
                    G.integrityTriggered = true;
                    G.integrityStoryStep = 0;
                    G.inIntegrityEvent = true;
                    G.integrityChoiceMade = false;
                    saveState();
                    renderIntegrityEvent();
                    return;
                }
            } else if (correct >= 4) {
                bonus = 15;
                evalText = '📋 良好';
                G.totalScore += bonus;
            } else if (correct >= 3) {
                bonus = 5;
                evalText = '📋 合格';
                G.totalScore += bonus;
            } else {
                bonus = -20;
                evalText = '⚠️ 不合格';
                G.totalScore += bonus;
                G.penaltyCount++;
                penaltyTrigger = true;
            }
            const newLevel = calcLevel(G.totalScore);
            G.levelIndex = newLevel;
            const yIdx = G.year - 1;
            const cIdx = G.currentCase;
            if (G.caseResults[yIdx]) {
                G.caseResults[yIdx][cIdx] = correct;
            }
            let penaltyMsg = '';
            if (penaltyTrigger) {
                const pIdx = G.penaltyCount - 1;
                if (pIdx < PENALTY_NAMES.length) {
                    penaltyMsg = `⚠️ 触发惩罚:${PENALTY_NAMES[pIdx]}`;
                    if (G.penaltyCount === 3) {
                        if (G.levelIndex > 0) G.levelIndex--;
                        penaltyMsg += ' → 降级!';
                    } else if (G.penaltyCount === 4) {
                        G.levelIndex = 0;
                        penaltyMsg += ' → 撤职!';
                    } else if (G.penaltyCount >= 5) {
                        G.gameOver = true;
                        G.endingType = 'fired';
                        saveState();
                        renderEnding();
                        return;
                    }
                }
            }
            if (G.levelIndex >= LEVELS.length - 1) {
                G.gameOver = true;
                G.endingType = 'promoted';
                saveState();
                renderEnding();
                return;
            }
            saveState();
            let msg = `📌 案件 ${G.currentCase+1}/${G.casesPerYear} 结束\n`;
            msg += `答对 ${correct}/5 题,评价:${evalText}\n`;
            msg += `积分变化:${bonus >= 0 ? '+' : ''}${bonus} 分\n`;
            msg += `当前积分:${G.totalScore} 级别:${getLevelName(G.levelIndex)}\n`;
            if (penaltyMsg) msg += penaltyMsg;
            G.feedbackMsg = msg;
            G.currentCase++;
            G.currentQuestion = 0;
            G.answered = true;
            saveState();
            renderCaseResult();
        }

        // ----- 廉政事件多步对话 -----
        const INTEGRITY_STORY = [
            { type: 'scene', text: '📍 税务局旁的小饭店,晚上7:30' },
            { type: 'narrator', text: '你刚破获完美案件,领导高兴地邀你吃饭。' },
            { type: 'dialogue', speaker: '领导', text: '小铁啊,这个案子你办得漂亮!来,我敬你一杯。' },
            { type: 'dialogue', speaker: '小铁', text: '领导过奖了,都是份内事。' },
            { type: 'dialogue', speaker: '领导', text: '你年轻有为,我很看好你。不过,有些事需要你“灵活”一点。比如,XX企业的老板是我的老朋友,你检查时高抬贵手...' },
            { type: 'dialogue', speaker: '小铁', text: '(内心挣扎)领导这是要我...' },
            { type: 'dialogue', speaker: '领导', text: '你放心,不会亏待你。只要你点头,下次晋升我第一个推荐你。' },
            { type: 'dialogue', speaker: '小铁', text: '(沉默片刻)我...我需要想一想。' },
            { type: 'narrator', text: '领导笑着拍了拍你的肩膀,等待你的答复。' },
        ];

        function renderIntegrityEvent() {
            if (!G || !G.inIntegrityEvent) return;
            const step = G.integrityStoryStep;
            const story = INTEGRITY_STORY;
            if (step >= story.length) {
                renderIntegrityChoice();
                return;
            }
            const s = story[step];
            let speaker = '', text = '', avatar = '', scene = '';
            if (s.type === 'scene') {
                scene = s.text;
            } else if (s.type === 'narrator') {
                speaker = '📖 叙述';
                avatar = '📜';
                text = s.text;
            } else if (s.type === 'dialogue') {
                speaker = s.speaker + ':';
                avatar = s.speaker === '小铁' ? '👤' : '👔';
                text = s.text;
            }
            let sceneHtml = scene ? `<div class="story-scene">${scene}</div>` : '';
            render(`
                <div id="status-bar">
                    <span>📅 第 ${G.year}/10 年</span>
                    <span>⭐ ${G.totalScore} 分</span>
                    <span>🎖 ${getLevelName(G.levelIndex)}</span>
                    <span>⚖️ 廉政考验</span>
                </div>
                <div class="panel">
                    <div style="font-size:16px;color:#f3c623;text-align:center;margin-bottom:12px;">⚖️ 廉政考验</div>
                    <div class="story-container">
                        ${sceneHtml}
                        <div style="display:flex;align-items:flex-start;gap:8px;">
                            <div class="pixel-avatar">${avatar}</div>
                            <div>
                                <div class="story-speaker">${speaker}</div>
                                <div class="story-dialogue">${text}</div>
                            </div>
                        </div>
                    </div>
                    <div class="btn-group">
                        <button class="pixel-btn" onclick="window._nextIntegrityStep()">📖 继续</button>
                    </div>
                </div>
            `);
        }

        window._nextIntegrityStep = function() {
            if (!G || !G.inIntegrityEvent) return;
            G.integrityStoryStep++;
            saveState();
            renderIntegrityEvent();
        };

        // 修改:选项只显示“接受”和“拒绝”,不带结果描述
        function renderIntegrityChoice() {
            if (!G) return;
            render(`
                <div id="status-bar">
                    <span>📅 第 ${G.year}/10 年</span>
                    <span>⭐ ${G.totalScore} 分</span>
                    <span>🎖 ${getLevelName(G.levelIndex)}</span>
                    <span>⚖️ 廉政抉择</span>
                </div>
                <div class="panel">
                    <div style="font-size:16px;color:#f3c623;text-align:center;margin-bottom:12px;">⚖️ 你将如何选择?</div>
                    <div class="story-container">
                        <div class="story-dialogue">领导期待地看着你,酒桌上气氛微妙...</div>
                    </div>
                    <div class="btn-group">
                        <button class="pixel-btn" onclick="window._chooseIntegrity('accept')">🍷 接受</button>
                        <button class="pixel-btn secondary" onclick="window._chooseIntegrity('reject')">🚫 拒绝</button>
                    </div>
                </div>
            `);
        }

        window._chooseIntegrity = function(choice) {
            if (!G) return;
            if (choice === 'accept') {
                G.acceptedBribe = true;
                G.bribeAcceptedYear = G.year;
                if (G.levelIndex < LEVELS.length - 1) {
                    G.levelIndex++;
                }
                if (G.levelIndex >= LEVELS.length - 1) {
                    G.gameOver = true;
                    G.endingType = 'promoted';
                    saveState();
                    renderEnding();
                    return;
                }
                G.inIntegrityEvent = false;
                G.integrityTriggered = true;
                G.feedbackMsg = '你接受了领导的“好意”,晋升一级!\n但今后每年结算时,都有50%概率被“一案双查”……';
                G.currentCase++;
                G.currentQuestion = 0;
                G.answered = true;
                saveState();
                renderCaseResult();
            } else {
                G.integrityRejected = true;
                const trigger = Math.random() < 0.5;
                if (trigger) {
                    G.doubleCaseYear = G.year + 1;
                    G.casesPerYear = 10;
                    G.feedbackMsg = '你拒绝了领导,领导表面上没说什么,但随后你被分配到更多案件!\n下一年案件数量翻倍!';
                } else {
                    G.feedbackMsg = '你拒绝了领导,坚守了廉洁底线。领导对你刮目相看。';
                }
                G.inIntegrityEvent = false;
                G.integrityTriggered = true;
                G.currentCase++;
                G.currentQuestion = 0;
                G.answered = true;
                saveState();
                renderCaseResult();
            }
        };

        // 年度结算,荣誉系统
        function renderYearEnd() {
            if (!G) return;
            if (G.acceptedBribe && Math.random() < 0.5) {
                G.gameOver = true;
                G.endingType = 'fired';
                saveState();
                renderEnding();
                return;
            }

            const year = G.year;
            const results = G.caseResults[year-1] || [];
            let totalCorrect = 0;
            for (let i=0; i<G.casesPerYear; i++) {
                if (results[i] !== undefined && results[i] >= 0) totalCorrect += results[i];
            }
            const maxPossible = G.casesPerYear * 5;
            const threshold = Math.floor(maxPossible * 0.7);
            let honorMsg = '';
            if (totalCorrect >= threshold) {
                G.honors.push(`第${year}年优秀公务员`);
                honorMsg = '🏅 获得“优秀公务员”荣誉!';
            }

            let summary = `📋 第 ${year} 年结束\n`;
            for (let i=0; i<G.casesPerYear; i++) {
                const r = results[i] !== undefined && results[i] >= 0 ? results[i] : '未完成';
                summary += `案件 ${i+1}: ${r}/5\n`;
            }
            summary += `\n本年度答对总数:${totalCorrect}/${maxPossible}\n`;
            summary += `当前积分: ${G.totalScore} 级别: ${getLevelName(G.levelIndex)}`;
            if (honorMsg) summary += '\n' + honorMsg;

            render(`
                <div id="status-bar">
                    <span>📅 第 ${G.year}/10 年</span>
                    <span>⭐ ${G.totalScore} 分</span>
                    <span>🎖 ${getLevelName(G.levelIndex)}</span>
                    <span>🏅 ${G.honors.length}</span>
                </div>
                <div class="panel">
                    <div style="font-size:16px;text-align:center;margin:6px 0 10px;color:#f3c623;">🏁 第 ${year} 年总结</div>
                    <div class="feedback-box" style="white-space:pre-line;font-size:13px;">${summary}</div>
                    <div class="btn-group">
                        <button class="pixel-btn" onclick="window._nextYear()">➡ 进入第 ${G.year+1} 年</button>
                    </div>
                </div>
            `);
        }

        window._nextYear = function() {
            if (!G || G.gameOver) return;
            if (G.year > 10) { retireGame(); return; }
            if (G.doubleCaseYear === G.year) {
                G.casesPerYear = 5;
                G.doubleCaseYear = 0;
            }
            G.year++;
            G.currentCase = 0;
            G.currentQuestion = 0;
            saveState();
            if (G.year > 10) { retireGame(); return; }
            if (G.doubleCaseYear === G.year) {
                G.casesPerYear = 10;
            } else {
                G.casesPerYear = 5;
            }
            saveState();
            renderMain();
        };

        function retireGame() {
            if (!G) return;
            G.gameOver = true;
            G.endingType = 'retired';
            saveState();
            renderEnding();
        }

        function renderCaseResult() {
            if (!G) return;
            const fb = G.feedbackMsg || '案件结算';
            const caseNum = G.currentCase;
            const totalCases = G.casesPerYear;
            render(`
                <div id="status-bar">
                    <span>📅 第 ${G.year}/10 年</span>
                    <span>⭐ ${G.totalScore} 分</span>
                    <span>🎖 ${getLevelName(G.levelIndex)}</span>
                    <span>📌 ${caseNum}/${totalCases} 案</span>
                    <span>🏅 ${G.honors.length}</span>
                </div>
                <div class="panel">
                    <div style="font-size:16px;text-align:center;margin:6px 0 10px;color:#f3c623;">⚖️ 案件结算</div>
                    <div class="feedback-box" style="white-space:pre-line;font-size:14px;">${fb}</div>
                    <div class="btn-group">
                        <button class="pixel-btn" onclick="window._startYear()">📂 继续</button>
                    </div>
                </div>
            `);
        }

        // ================================================================
        //  5. 渲 染 函 数
        // ================================================================
        const app = document.getElementById('app');
        function render(template) { app.innerHTML = template; }

        function renderTitle() {
            render(`
                <div style="text-align:center;padding:26px 10px 18px;">
                    <h1 style="font-size:32px;color:#f3c623;text-shadow:4px 4px 0 #533483;">📟 税务稽查<br>风云录</h1>
                    <div style="color:#b0b0d0;font-size:12px;margin:16px 0;background:#0f1a2f;padding:12px;border:3px solid #2a2a4a;">
                        🕵️ 角色扮演 · 十年稽查路
                    </div>
                    <div class="pixel-divider"></div>
                    <div class="btn-group">
                        <button class="pixel-btn" onclick="window._gameStart()">▶ 开始游戏</button>
                        <button class="pixel-btn secondary" onclick="window._gameContinue()">📂 继续档案</button>
                    </div>
                </div>
            `);
        }

        function renderMain() {
            if (!G) return;
            const year = G.year;
            const level = getLevelName(G.levelIndex);
            const casesDone = G.currentCase;
            const totalCases = G.casesPerYear;
            const progress = Math.min(100, Math.round(((year-1)*totalCases + casesDone) / (10*totalCases) * 100));
            render(`
                <div id="status-bar">
                    <span>📅 第 ${year}/10 年</span>
                    <span>⭐ ${G.totalScore} 分</span>
                    <span>🎖 ${level}</span>
                    <span>📊 ${casesDone}/${totalCases} 案</span>
                    <span>🏅 ${G.honors.length}</span>
                </div>
                <div class="panel" style="text-align:center;padding:24px 16px;">
                    <div style="font-size:14px;margin-bottom:12px;">🗂 稽查档案</div>
                    <div style="font-size:24px;color:#f3c623;margin:8px 0;">${level}</div>
                    <div style="font-size:12px;color:#b0b0d0;">年度进度 ${progress}%</div>
                    <div style="width:100%;height:12px;background:#0a0a1a;border:3px solid #2a2a4a;margin:14px 0;">
                        <div style="width:${progress}%;height:100%;background:#f3c623;transition:width 0.2s;"></div>
                    </div>
                    <div style="margin:6px 0;font-size:10px;color:#f3c623;">
                        ${G.honors.map(h => `<span class="honor-badge">${h}</span>`).join('')}
                    </div>
                    <div class="btn-group">
                        <button class="pixel-btn" onclick="window._startYear()">📂 继续办案</button>
                        <button class="pixel-btn secondary" onclick="window._resetGame()">🔄 重新开始</button>
                    </div>
                </div>
            `);
        }

        function renderEnding() {
            if (!G) return;
            let title = '', cls = '', desc = '';
            if (G.endingType === 'promoted') {
                title = '🎉 晋升局长!';
                cls = 'ending-gold';
                desc = '你成功晋升为税务局局长!\n十年稽查路,终成一代传奇。';
                if (G.honors.length > 0) desc += `\n荣誉:${G.honors.join('、')}`;
            } else if (G.endingType === 'fired') {
                title = '🔥 开除处分';
                cls = 'ending-fire';
                let reason = '因多次违规,被开除公职。';
                if (G.acceptedBribe) reason = '因接受贿赂被“一案双查”,开除公职。';
                desc = reason + '\n稽查之路,就此终结。';
            } else {
                title = '📜 光荣退休';
                cls = 'ending-silver';
                desc = `完成十年稽查生涯,光荣退休!\n最终级别:${getLevelName(G.levelIndex)} 积分:${G.totalScore}`;
                if (G.honors.length > 0) desc += `\n荣誉:${G.honors.join('、')}`;
            }
            render(`
                <div class="panel" style="text-align:center;padding:30px 16px;">
                    <div class="ending-title ${cls}">${title}</div>
                    <div class="pixel-divider"></div>
                    <div class="feedback-box" style="white-space:pre-line;font-size:15px;margin:16px 0;">${desc}</div>
                    <div style="font-size:12px;color:#8a8aaa;margin:10px 0;">总积分:${G.totalScore} 最终级别:${getLevelName(G.levelIndex)}</div>
                    <div class="btn-group"><button class="pixel-btn" onclick="window._resetGame()">🔄 重新开始</button></div>
                </div>
            `);
        }

        // ================================================================
        //  6. 全 局 函 数
        // ================================================================
        window._gameStart = function() { initGame(); };
        window._gameContinue = function() {
            const data = loadState();
            if (data) { restoreFromData(data); } else { alert('没有存档,开始新游戏。'); initGame(); }
        };
        window._startYear = function() { startYear(); };
        window._resetGame = function() {
            if (confirm('确定重新开始吗?')) { clearState(); G = null; renderTitle(); }
        };

        // ================================================================
        //  7. 初 始 化
        // ================================================================
        const saved = loadState();
        if (saved && !saved.gameOver) {
            renderTitle();
        } else if (saved && saved.gameOver) {
            G = defaultState();
            Object.assign(G, saved);
            if (!G.honors) G.honors = [];
            renderEnding();
        } else {
            renderTitle();
        }
        console.log('📟 税务稽查风云录 (优化版) 加载完成');
    })();
</script>
</body>
</html>

Game Source: 税务稽查风云录 · 角色扮演

Creator: SparkGlider58

Libraries: none

Complexity: complex (1167 lines, 73.4 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-sparkglider58" to link back to the original. Then publish at arcadelab.ai/publish.