税务稽查风云录 · 角色扮演
by SparkGlider581167 lines73.4 KB
<!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.