专栏文章

《OJ 运维模拟器 V4.1》

个人记录参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mm38yvc5
此快照首次捕获于
2026/02/26 17:14
2 周前
此快照最后确认于
2026/02/26 17:14
2 周前
查看原文

《OJ 运维模拟器 V4.1》

《OJ 运维模拟器 V4.1》由chen_zhe《OJ 运维模拟器》美化优化而成。
添加了借贷,辞退等一系列功能,并美化为UI响应式。
源码如下CPP
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OJ 运维模拟器 V4.1</title>
    <style>
        :root {
    --bg-color: #f6f7fb;
    --panel-bg: #ffffff;
    --accent: #2563eb;
    --accent-2: #0ea5e9;
    --text: #111827;
    --text-light: #111827;
    --muted: #6b7280;
    --success: #16a34a;
    --warning: #f59e0b;
    --danger: #ef4444;
    --border: #e5e7eb;
    --shadow: 0 8px 24px rgba(17,24,39,0.08);
}
body {

            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: var(--bg-color);
            color: var(--text-light);
            margin: 0;
            display: flex;
            height: 100vh;
            overflow: hidden;
        }
        ::-webkit-scrollbar { width: 8px; }
        ::-webkit-scrollbar-track { background: transparent; }
        ::-webkit-scrollbar-thumb { background: rgba(17,24,39,0.18); border-radius: 999px; }
        
        #sidebar {
            width: 350px;
            background-color: #fbfdff;
            padding: 20px;
            overflow-y: auto;
            border-right: 1px solid var(--border);
            flex-shrink: 0;
            display: flex;
            flex-direction: column;
            gap: 12px;
        }
        #main {
            flex-grow: 1;
            display: flex;
            flex-direction: column;
            padding: 20px;
            overflow-y: auto;
            background: var(--bg-color);
            padding-bottom: 20px;
        }

        h2, h3 { margin: 0 0 8px 0; color: var(--text); font-size: 1.1em; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
        .stat-box { background: var(--panel-bg); padding: 12px; border-radius: 12px; font-size: 0.9em; border: 1px solid var(--border); box-shadow: var(--shadow); }
        .stat-row { display: flex; justify-content: space-between; margin-bottom: 4px; align-items: center; }
        .stat-val { font-weight: 800; color: var(--accent); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 1.08em; }
        .sub-text { font-size: 0.8em; color: var(--muted); }
        
        .forecast-row { display: flex; justify-content: space-between; font-size: 0.85em; color: var(--muted); border-bottom: 1px dashed var(--border); padding: 4px 0; }
        .forecast-val { font-family: monospace; }
        .income { color: #16a34a; }
        .expense { color: #ef4444; }

        button { background-color: var(--accent); color: white; border: 1px solid rgba(17,24,39,0.12); padding: 8px 12px; cursor: pointer; border-radius: 10px; transition: transform 0.12s ease, box-shadow 0.12s ease, background-color 0.12s ease; margin: 2px; font-size: 0.92em; box-shadow: 0 6px 14px rgba(37,99,235,0.18); }
        button:hover { background-color: #1d4ed8; transform: translateY(-1px); box-shadow: 0 10px 18px rgba(37,99,235,0.22); }
        button:disabled { background-color: #cbd5e1; cursor: not-allowed; opacity: 0.7; transform: none; box-shadow: none; }
        
        .btn-group { display: flex; align-items: center; margin-bottom: 6px; }
        .btn-group label { width: 80px; font-size: 0.9em; color: #ccc; }
        .btn-small { padding: 6px 10px; font-size: 0.82em; background-color: #64748b; box-shadow: none; }
        
        .tabs { display: flex; border-bottom: 1px solid var(--border); margin-bottom: 16px; gap: 6px; }
        .tab { padding: 10px 14px; cursor: pointer; background: #eef2ff; border: 1px solid var(--border); border-bottom: none; border-radius: 12px 12px 0 0; }
        .tab.active { background: var(--accent); color: white; font-weight: 700; border-color: rgba(37,99,235,0.25); }
        
        .panel { display: none; animation: fadeIn 0.3s; }
        .panel.active { display: block; }
        @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
        
        .action-card { background: var(--panel-bg); border: 1px solid var(--border); padding: 16px; margin-bottom: 16px; border-radius: 14px; box-shadow: var(--shadow); }
        
        
#log-fab{
    position: fixed;
    right: 18px;
    bottom: 18px;
    z-index: 1200;
    border-radius: 999px;
    padding: 10px 14px;
    font-weight: 800;
    box-shadow: 0 10px 22px rgba(17,24,39,0.14);
}
#log-float{
    position: fixed;
    right: 18px;
    bottom: 70px;
    width: 480px;
    height: 560px;
    max-width: calc(100vw - 40px);
    max-height: calc(100vh - 120px);
    background: rgba(255,255,255,0.88);
    border: 1px solid rgba(229,231,235,0.9);
    border-radius: 16px;
    box-shadow: 0 20px 60px rgba(17,24,39,0.18);
    backdrop-filter: blur(14px);
    display: none;
    overflow: hidden;
    z-index: 1201;
    resize: both;
}
#log-float.show{ display: flex; flex-direction: column; }

#log-float-header{
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 12px;
    gap: 10px;
    border-bottom: 1px solid var(--border);
    cursor: move;
    user-select: none;
}
#log-float-header .title{
    font-weight: 900;
    color: var(--text);
    letter-spacing: 0.2px;
}
#log-float-header .actions{ display: flex; gap: 8px; align-items: center; }
#log-float-header .actions button{
    background: transparent;
    color: var(--text);
    border: 1px solid var(--border);
    box-shadow: none;
    padding: 6px 10px;
}
#log-float-header .actions button:hover{
    background: rgba(37,99,235,0.10);
}

#log-area{
    flex: 1;
    height: auto;
    background: transparent;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    padding: 12px;
    overflow-y: auto;
    color: var(--text);
    font-size: 0.92em;
}
.log-entry { margin-bottom: 8px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
.log-bad { color: #dc2626; }
.log-good { color: #16a34a; }
.log-warn { color: #f59e0b; }
#warning-banner { background-color: rgba(239, 68, 68, 0.12); border: 1px solid rgba(239,68,68,0.35); color: #991b1b; padding: 14px; margin-bottom: 16px; border-radius: 12px; display: none; font-weight: 700; }
        @keyframes pulse { 0% { opacity: 0.8; } 50% { opacity: 1; } 100% { opacity: 0.8; } }

        #next-month-btn { width: 100%; padding: 16px; font-size: 1.15em; font-weight: 800; background-color: var(--success); border: none; margin-bottom: 12px; border-radius: 14px; box-shadow: 0 10px 18px rgba(22,163,74,0.22); }
        #next-month-btn:hover { background-color: #15803d; }
        
        .progress-container { width: 100%; background-color: #e5e7eb; height: 8px; border-radius: 999px; margin-top: 10px; overflow: hidden; }
        .progress-bar { height: 100%; background-color: var(--accent-2); width: 0%; transition: width 0.5s ease-out; }
        
        .edu-card { background: #f8fafc; padding: 12px; margin-bottom: 10px; border-radius: 12px; border: 1px solid var(--border); box-shadow: 0 6px 18px rgba(17,24,39,0.06); }
        .edu-header { display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 8px; }
        .edu-cost { font-size: 0.85em; color: #aaa; margin-bottom: 8px; line-height: 1.4; }
        .edu-actions { display: flex; gap: 8px; }
        .btn-run { background-color: #2e7d32; flex: 1; }
        .btn-up { background-color: #1565c0; flex: 1; }

/* ===== Responsive layout ===== */
@media (max-width: 900px){
  body{ flex-direction: column; height: auto; overflow: auto; }
  #sidebar{
    width: 100%;
    border-right: none;
    border-bottom: 1px solid var(--border);
    max-height: none;
  }
  #main{
    padding: 14px;
    overflow: visible;
  }
  .tabs{ flex-wrap: wrap; gap: 8px; }
  .tab{ flex: 1 1 120px; text-align: center; }
  .btn-group{ flex-wrap: wrap; gap: 6px; }
  .btn-group label{ width: 100%; color: var(--muted); }
  /* floating log fits phone */
  #log-float{ left: 10px; right: 10px; width: auto; bottom: 70px; height: 70vh; }
}
@media (max-width: 480px){
  #sidebar{ padding: 14px; }
  #main{ padding: 12px; }
  #log-fab{ right: 12px; bottom: 12px; }
}

/* ===== Responsive: panes/cards/buttons/inputs ===== */
:root{ --content-max: 1200px; }

/* prevent long rows from breaking layout */
.stat-row, .forecast-row{ gap: 10px; flex-wrap: wrap; }
.stat-row > span:first-child, .forecast-row > span:first-child{ min-width: 120px; }
.stat-row > span:last-child{ text-align: right; }
.forecast-row > span:last-child{ text-align: right; }

/* make main content not too wide on huge screens */
@media (min-width: 1300px){
  #main{ max-width: var(--content-max); margin: 0 auto; }
}

/* medium screens: shrink sidebar a bit */
@media (max-width: 1100px){
  #sidebar{ width: 320px; }
}

/* tablets/phones: stack + make every “pane” comfortable */
@media (max-width: 900px){
  .action-card{ padding: 14px; border-radius: 14px; }
  h2, h3{ font-size: 1.02em; }
}

/* small phones: buttons/inputs full width, avoid overflow */
@media (max-width: 640px){
  /* Tabs already wrap; make each tab readable */
  .tab{ padding: 10px 10px; }

  /* action card content */
  #main .action-card button{
    width: 100%;
    margin: 6px 0;
  }
  #main .action-card .btn-small{
    width: 100%;
    margin: 6px 0;
  }

  /* loan inputs become full width */
  #loan-amt, #loan-term, #repay-amt{
    width: 100% !important;
    max-width: none !important;
    box-sizing: border-box;
  }

  /* keep log header buttons compact */
  #log-float-header .actions button{
    width: auto;
    margin: 0;
  }

  /* reduce padding in sidebar cards */
  .stat-box{ padding: 10px; }
}

/* extra tiny: log float + fab spacing */
@media (max-width: 420px){
  #log-float{ bottom: 62px; height: 74vh; }
  #log-fab{ padding: 10px 12px; }
}

</style>
</head>
<body>

<div id="sidebar">
    <div class="stat-box" style="background: var(--accent); text-align: center;">
        <h2 style="border:none; margin:0; color:#fff;">OJ 运维模拟器</h2>
        <div style="font-size:0.8em; opacity:0.85; display:flex; justify-content:center; gap:12px; flex-wrap:wrap;">
            <span>目标: 存活至 2026-01</span>
            <span>来自 <a href="https://www.luogu.com.cn/user/1271480" target="_blank" rel="noopener" style="color:#fff; text-decoration: underline; font-weight:800;">Trie2025</a></span>
        </div>
    </div>

    <div class="stat-box">
        <div class="stat-row"><span>📅 日期</span> <span id="disp-date" class="stat-val">2013-01</span></div>
        <div class="stat-row"><span>💰 资金</span> <span id="disp-money" class="stat-val">2000</span></div>
        <div class="stat-row"><span>🏦 贷款</span> <span id="disp-loan" class="stat-val">0</span></div>
        <div class="stat-row"><span>⭐ 声誉</span> <span id="disp-rep" class="stat-val">0.000</span></div>
        <div class="progress-container"><div id="rep-bar" class="progress-bar" style="width: 50%;"></div></div>
    </div>

    <!-- 故障监控 -->
    <div class="stat-box" style="border: 1px solid #555;">
        <div class="stat-row"><span>⚠️ 连续故障月数</span> <span id="disp-fail-months" style="color:#aaa; font-weight:bold">0 / 6</span></div>
        <div style="font-size:0.75em; color:#888;">若连续6个月硬盘满或服务器/评测机过载,游戏强制结束。</div>
    </div>

    <!-- 预测模块 -->
    <h3>🔮 下月预测</h3>
    <div class="stat-box">
        <div class="forecast-row"><span>预计收入(低保+API)</span> <span id="fc-income" class="forecast-val income">+0</span></div>
        <div class="forecast-row"><span>预计工资支出</span> <span id="fc-salary" class="forecast-val expense">-0</span></div>
        <div class="forecast-row"><span>预计维护支出</span> <span id="fc-maint" class="forecast-val expense">-0</span></div>
        <div class="forecast-row"><span>预计贷款利息</span> <span id="fc-loan-int" class="forecast-val expense">-0</span></div>
        <div class="stat-row" style="margin-top:5px; border-top:1px solid #444; padding-top:5px;">
            <span>预计净收支:</span> <span id="fc-net" class="stat-val">0</span>
        </div>
        <div style="font-size:0.8em; color:#888; margin-top:5px;">
            研发维护: <span id="fc-rd-cost">0</span>/月<br>
            学术需求: <span id="fc-acad-cost">0</span>/月<br>
            社区需求: <span id="fc-comm-cost">0</span>/月
        </div>
    </div>

    <h3>⚡ 资源池</h3>
    <div class="stat-box">
        <div class="stat-row"><span>行动力 (AP)</span> <span id="disp-ap" class="stat-val" style="color: #ffca28;">100</span></div>
        <div class="stat-row"><span>研发点</span> <span id="disp-rd" class="stat-val">0</span></div>
        <div class="stat-row"><span>学术点</span> <span id="disp-acad" class="stat-val">0</span></div>
        <div class="stat-row"><span>社区点</span> <span id="disp-comm" class="stat-val">0</span></div>
    </div>

    <h3>📊 运营数据</h3>
    <div class="stat-box">
        <div class="stat-row"><span>总用户</span> <span id="disp-users-total" class="stat-val">50</span></div>
        <div class="stat-row"><span class="sub-text">上月新增提交</span> <span id="disp-last-subs" style="color:#fff">0</span></div>
        <div class="stat-row"><span class="sub-text">上月新增题解</span> <span id="disp-last-sols" style="color:#fff">0</span></div>
        <hr style="border-color:#444; margin: 8px 0;">
        <div class="stat-row"><span>题库 (高/中/低)</span> <span><span id="disp-prob-high">0</span>/<span id="disp-prob-mid">0</span>/<span id="disp-prob-low">0</span></span></div>
        <div class="stat-row"><span class="sub-text">硬盘占用</span> <span id="disp-disk">0 GB</span></div>
    </div>

    <h3>🖥️ 服务器</h3>
    <div class="stat-box">
        <div class="stat-row"><span>Web核心</span> <span><span id="disp-server-core" class="stat-val">2</span> 核 (Lv.<span id="disp-server-arch">1</span>)</span></div>
        <div class="stat-row"><span>评测核心</span> <span><span id="disp-judge-core" class="stat-val">1</span> 核 (Lv.<span id="disp-judge-arch">1</span>)</span></div>
        <div class="stat-row" style="justify-content: flex-end; font-size: 0.8em; color: #888;">Web负载: <span id="disp-server-load">0</span> / <span id="disp-server-cap">0</span></div>
        <div class="stat-row" style="justify-content: flex-end; font-size: 0.8em; color: #888;">评测负载: <span id="disp-judge-load">0</span> / <span id="disp-judge-cap">0</span></div>
    </div>
</div>

<div id="main">
    <div id="warning-banner">⚠️ 警告:预计下个月将破产!请立即筹集资金!</div>
    
    <button id="next-month-btn" onclick="game.nextMonth()">进入下个月</button>

    <div class="tabs">
        <div class="tab active" onclick="ui.switchTab('ops')">日常运营</div>
        <div class="tab" onclick="ui.switchTab('rd')">研发中心</div>
        <div class="tab" onclick="ui.switchTab('hr')">人才招募</div>
        <div class="tab" onclick="ui.switchTab('edu')">网校课程</div>
</div>

    <!-- Operations Tab -->
    <div id="tab-ops" class="panel active">
        <div class="action-card">
            <h3>📢 资源转化 (每次 10 行动力)</h3>
            <button onclick="game.convertAP('rd')">转研发 (+10)</button>
            <button onclick="game.convertAP('acad')">转学术 (+10)</button>
            <button onclick="game.convertAP('comm')">转社区 (+10)</button>
            <div style="margin-top:8px; display:flex; flex-wrap:wrap; gap:8px;">
                <button class="btn-small" onclick="game.convertAPAll('rd')" style="background:#475569">一键转研发</button>
                <button class="btn-small" onclick="game.convertAPAll('acad')" style="background:#475569">一键转学术</button>
                <button class="btn-small" onclick="game.convertAPAll('comm')" style="background:#475569">一键转社区</button>
                <div style="font-size:0.8em; color: var(--muted); align-self:center;">(会把当前 AP 按 10 点一组全部转化)</div>
            </div>

            <button onclick="game.fundraising()" style="float:right; background:#5d4037">号召募捐 (50 行动力, 降声誉)</button>
            <div style="clear:both; margin-top:5px; font-size:0.8em; color:#aaa;">
                募捐获得金钱数: 用户数 × (声誉+1) × 5
            </div>
        </div>


        
<div class="action-card">
    <h3>🏦 借贷中心</h3>
    <div class="card-desc" style="font-size:0.85em; color: var(--muted); margin-bottom:10px;">
        贷款会产生月利息(复利)。现在支持自定义借款额度与还款时限;到期将强制还款,余额不足则游戏结束。
    </div>

    <div style="display:flex; flex-wrap:wrap; gap:8px; align-items:center;">
        <span style="min-width:72px; color: var(--muted);">借款:</span>
        <input id="loan-amt" type="number" min="1" step="100" placeholder="金额" style="width:140px; padding:8px 10px; border:1px solid var(--border); border-radius:10px; background:#fff;">
        <input id="loan-term" type="number" min="1" max="24" step="1" placeholder="期限(月)" style="width:120px; padding:8px 10px; border:1px solid var(--border); border-radius:10px; background:#fff;">
        <button onclick="game.borrowLoanCustom()">借款</button>
        <span style="font-size:0.85em; color: var(--muted);">每次操作消耗 <span style="font-family:monospace; font-weight:700;">10 AP</span></span>
    </div>

    <div style="display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:10px;">
        <span style="min-width:72px; color: var(--muted);">还款:</span>
        <input id="repay-amt" type="number" min="1" step="100" placeholder="金额" style="width:140px; padding:8px 10px; border:1px solid var(--border); border-radius:10px; background:#fff;">
        <button onclick="game.repayLoanCustom()" style="background:#0f766e">还款</button>
        <button onclick="game.repayLoan('all')" class="btn-small" style="background:#115e59">全部</button>
    </div>

    <div style="margin-top:10px; font-size:0.85em; color: var(--muted); line-height:1.5;">
        当前可借上限:<span id="disp-loan-limit" style="font-family:monospace; font-weight:700; color: var(--accent);">0</span>,
        月利率:<span id="disp-loan-rate" style="font-family:monospace; font-weight:700;">0%</span>,
        剩余期限:<span id="disp-loan-term" style="font-family:monospace; font-weight:700;">—</span>。
    </div>
</div>


        <div class="action-card">
            <h3>🏆 赛事运营</h3>
            <div class="card-desc">举办比赛可提升下月用户增长速度。</div>
            <button onclick="game.organizeContest('old')">原题赛 (需要$500, 学术40)<br><span style="font-size:0.8em">普通题目+4, 增长x1.05</span></button>
            <button onclick="game.organizeContest('water')">水赛 (需要$1000, 学术40)<br><span style="font-size:0.8em">低质题目+6, 增长x1.15</span></button>
            <button onclick="game.organizeContest('normal')">普通赛 (需要$3000, 学术80)<br><span style="font-size:0.8em">普通题目+4, 增长x1.1</span></button>
            <button onclick="game.organizeContest('grand')">优秀赛 (需要$8000, 学术200)<br><span style="font-size:0.8em">高质题目+3, 增长x1.1, 升声誉</span></button>
        </div>
        
        <div class="action-card">
            <h3>📝 题库扩充</h3>
            <div class="card-desc" style="font-size:0.85em; color:#aaa; margin-bottom:5px;">搬运普通/低质试题可吸引 10~50 名新用户。</div>
            <button onclick="game.addProblem('high')">高质量 (+3题, -20学术)</button>
            <button onclick="game.addProblem('mid')">普通 (+10题, -20学术)</button>
            <button onclick="game.addProblem('low')">低质量 (+20题, -20学术)</button>
        </div>
    </div>

    <!-- R&D Tab -->
    <div id="tab-rd" class="panel">
        <div class="action-card">
            <h3>💻 基础设施</h3>
            <div style="color:#ff5252; font-size:0.85em; margin-bottom:10px; font-weight:bold;">
                ⚠️ 注意:当核心数达到架构上限(3264 × 架构等级)时,必须先升级架构才能继续购买。
            </div>
            
            <div class="btn-group">
                <label>Web核心:</label>
                <button onclick="game.expandHardware('server', 1)">+1</button>
                <button onclick="game.expandHardware('server', 10)" class="btn-small">+10</button>
                <button onclick="game.expandHardware('server', 'max')" class="btn-small" style="background:#555">填满</button>
            </div>
            
            <div class="btn-group">
                <label>评测核心:</label>
                <button onclick="game.expandHardware('judge', 1)">+1</button>
                <button onclick="game.expandHardware('judge', 10)" class="btn-small">+10</button>
                <button onclick="game.expandHardware('judge', 'max')" class="btn-small" style="background:#555">填满</button>
            </div>
            
            <div class="btn-group" style="margin-top:10px">
                <button onclick="game.expandHardware('disk', 1)">加硬盘 (+40GB)</button>
                <button onclick="game.expandHardware('disk', 25)">加硬盘 (+1000GB)</button>
            </div>
        </div>
        <div class="action-card">
            <h3>🚀 功能研发</h3>
            <div id="rd-list"></div>
        </div>
        <div class="action-card">
            <h3>🏗️ 架构升级</h3>
            <div class="card-desc">架构升级后,每个核心可额外容纳 100 活跃用户。</div>
            <div style="font-size:0.85em; color:#aaa; margin-bottom:8px; line-height:1.5;">
                Web下级需求: <span id="disp-up-server-cost" style="color:#ffb74d"></span><br>
                评测下级需求: <span id="disp-up-judge-cost" style="color:#ffb74d"></span>
            </div>
            <button onclick="game.upgradeArch('server')">Web架构升级</button>
            <button onclick="game.upgradeArch('judge')">评测架构升级</button>
        </div>
    </div>

    <!-- HR Tab -->
    <div id="tab-hr" class="panel">
        <div class="action-card">
            <h3>招聘市场 (消耗 10 AP,兼职人数有上限!)</h3>
            <div style="font-size:0.85em; color: var(--muted); margin-bottom:10px;">辞退:消耗该人才一个月工资的 50% + 30 AP。</div>
            <h4 style="margin-top:0">研发团队 (产出: <span id="out-rd">0</span>/月)</h4>
            <button onclick="game.hire('rd_student')">高中生 ($1500, +20 研发)</button> <button class="btn-small" onclick="game.fire('rd_student')" style="background: var(--danger); box-shadow:none;">辞退</button>
            <button onclick="game.hire('rd_uni')">大学生 ($5000, +75 研发)</button> <button class="btn-small" onclick="game.fire('rd_uni')" style="background: var(--danger); box-shadow:none;">辞退</button>
            <button onclick="game.hire('rd_full')">全职大牛 ($12000, +120 研发)</button> <button class="btn-small" onclick="game.fire('rd_full')" style="background: var(--danger); box-shadow:none;">辞退</button>
            
            <h4 style="margin-top:10px">学术团队 (产出: <span id="out-acad">0</span>/月)</h4>
            <button onclick="game.hire('acad_part')">现役选手 ($500, +50 学术)</button> <button class="btn-small" onclick="game.fire('acad_part')" style="background: var(--danger); box-shadow:none;">辞退</button>
            <button onclick="game.hire('acad_full')">全职教练 ($15000, +400 学术)</button> <button class="btn-small" onclick="game.fire('acad_full')" style="background: var(--danger); box-shadow:none;">辞退</button>
            
            <h4 style="margin-top:10px">社区团队 (产出: <span id="out-comm">0</span>/月)</h4>
            <button onclick="game.hire('comm_part')">兼职版主 ($500, +50 社区)</button> <button class="btn-small" onclick="game.fire('comm_part')" style="background: var(--danger); box-shadow:none;">辞退</button>
            <button onclick="game.hire('comm_full')">全职运营 ($7500, +400 社区)</button> <button class="btn-small" onclick="game.fire('comm_full')" style="background: var(--danger); box-shadow:none;">辞退</button>
        </div>
    </div>

    <!-- Edu Tab -->
    <div id="tab-edu" class="panel">
        <div class="action-card">
            <h3>🏫 网校管理</h3>
            <p style="font-size:0.85em; color:#aaa">研发[网校功能]后解锁。课程升级后带来更多收入,但升级成本也会增加。开课后有 3 个月冷却期。</p>
            <div id="edu-list"></div>
        </div>
    </div>
</div> <!-- end #main -->

<button id="log-fab" class="btn-small" onclick="ui.toggleLog()" title="打开/关闭日志">📜 日志</button>

<div id="log-float" role="dialog" aria-label="日志浮窗">
  <div id="log-float-header">
    <span class="title">📜 日志</span>
    <div class="actions">
      <button onclick="ui.clearLog()">清空</button>
      <button onclick="ui.toggleLog()">关闭</button>
    </div>
  </div>
  <div id="log-area">
    <div class="log-entry" style="color: var(--accent-2); font-weight:800;">💡 欢迎来到 OJ 运维模拟器!</div>
    <div class="log-entry">请密切关注左侧的【下月预测】面板,避免破产。</div>
  </div>
</div>

<script>
const GAME_PARAMS = {
    INITIAL: { YEAR: 2013, MONTH: 1, MONEY: 2000, AP: 100, USERS: 50, SERVER_CORES: 2, JUDGE_CORES: 1, DISK_MB: 20480 },
    INCOME: { MONTHLY_GRANT: 500 },
    LOAN: { BASE_LIMIT: 5000, PER_USER: 0.8, RATE: 0.02, AP_COST: 10 },
    COSTS: {
        SERVER_CORE_RENT: 200, JUDGE_CORE_RENT: 200, DISK_BLOCK_RENT: 100, DISK_BLOCK_SIZE_GB: 40,
        ADD_PROBLEM_ACAD: 20, HIRE_AP: 10
    },
    HIRING: {
        rd_student: { name: "高中兼职", cost: 1500, gain: 20, limit: 5, type: 'rd' },
        rd_uni:     { name: "大学兼职", cost: 5000, gain: 75, limit: 5, type: 'rd' },
        rd_full:    { name: "全职开发", cost: 12000, gain: 120, limit: 999, type: 'rd' },
        acad_part:  { name: "现役选手", cost: 500, gain: 50, limit: 20, type: 'acad' },
        acad_full:  { name: "全职教练", cost: 15000, gain: 400, limit: 999, type: 'acad' },
        comm_part:  { name: "兼职版主", cost: 500, gain: 50, limit: 5, type: 'comm' },
        comm_full:  { name: "全职运营", cost: 7500, gain: 400, limit: 999, type: 'comm' }
    },
    CONTESTS: {
        old:    { name: "原题赛", cost: 500, acad: 40, prob_mid: 4, rep_pen: 0.1, growth_mult: 1.05 },
        water:  { name: "水赛", cost: 1000, acad: 40, prob_low: 6, rep_pen: 0.1, growth_mult: 1.15 },
        normal: { name: "普通赛", cost: 3000, acad: 80, prob_mid: 4, growth_mult: 1.1 },
        grand:  { name: "优秀赛", cost: 8000, acad: 200, prob_high: 3, rep_gain: 0.01, growth_mult: 1.1 }
    },
    FEATURES: {
        difficulty: { id: 'difficulty', name: "难度功能", cost: 1000, rd: 100, maintain: 0, desc: "让 OJ 支持难度评级,需要少量学术维护,可以吸引更多的用户使用你的 OJ" },
        solution:   { id: 'solution', name: "题解功能", cost: 1000, rd: 100, maintain: 10, desc: "让 OJ 可以显示题解,需要学术维护,可以吸引更多的用户使用你的 OJ" },
        discuss:    { id: 'discuss', name: "讨论区", cost: 5000, rd: 300, maintain: 20, desc: "让 OJ 有讨论区,需要社区维护,可以吸引更多的用户使用你的 OJ" },
        team:       { id: 'team', name: "团队功能", cost: 10000, rd: 500, maintain: 30, desc: "让 OJ 拥有团队功能,可以吸引学校/机构使用 OJ,但是团队功能会降低服务器承载力" },
        school:     { id: 'school', name: "网校功能", cost: 20000, rd: 800, maintain: 40, desc: "解锁网校课程" },
        api:        { id: 'api', name: "评测API", cost: 30000, rd: 1500, maintain: 50, desc: "出售外部评测服务,获得大量稳定现金流,但是会降低服务器承载力" }
    },
    USER_BEHAVIOR: {
        SUBMISSIONS: { normal: 15, active: 65, core: 300 },
        SOLUTIONS: { normal: 0.01, active: 1, core: 5 },
        GROWTH_FACTOR: 0.25, 
        UPGRADE_N_A_BASE: 4, UPGRADE_N_A_MULT: 10, UPGRADE_A_C_MULT: 5
    },
    DISK_USAGE: { PROBLEM: 50, USER_DATA: 0.05, SUBMISSION: 0.002, SOLUTION: 0.005 },
    CLASSES: {
        intro: { name: "入门课", max: 3, cost_base: 3000, acad_base: 100, inc_base: 10000, req_money: 4000, req_acad: 100 },
        basic: { name: "基础课", max: 3, cost_base: 5000, acad_base: 150, inc_base: 15000, req_money: 6000, req_acad: 150 },
        advanced: { name: "提高课", max: 5, cost_base: 10000, acad_base: 200, inc_base: 25000, req_money: 10000, req_acad: 240 },
        sprint: { name: "冲刺班", max: 3, cost_base: 20000, acad_base: 200, inc_base: 40000, req_money: 20000, req_acad: 200, season: [7,8,9,10] },
        national: { name: "国赛班", max: 5, cost_base: 25000, acad_base: 300, inc_base: 50000, req_money: 40000, req_acad: 400 }
    }
};

const game = {
    date: { year: GAME_PARAMS.INITIAL.YEAR, month: GAME_PARAMS.INITIAL.MONTH },
    money: GAME_PARAMS.INITIAL.MONEY,
    loan_balance: 0,
    loan_term_left: 0,
    ap: GAME_PARAMS.INITIAL.AP,
    resources: { rd: 0, acad: 0, comm: 0 },
    stats: {
        users: { normal: GAME_PARAMS.INITIAL.USERS, active: 0, core: 0 },
        problems: { high: 0, mid: 0, low: 0 },
        total_submissions: 0,
        total_solutions: 0,
        reputation: 0,
        last_month: { subs: 0, sols: 0 },
        consecutive_failure_months: 0,
        comm_fail_months: 0, // 新增:累计社区违规月数
        pending_user_bonus: 0 // 新增:下月用户奖励
    },
    hardware: {
        server_cores: GAME_PARAMS.INITIAL.SERVER_CORES, judge_cores: GAME_PARAMS.INITIAL.JUDGE_CORES,
        disk_extra_blocks: 0, disk_used: 0, server_arch: 1, judge_arch: 1
    },
    staff: {}, class_state: {}, features_unlocked: {},
    flags: {
        fundraising_penalty_val: 0,
        temp_rep_penalty: 0,
        contest_growth_mult: 1.0
    },

    init: function() {
        for(let k in GAME_PARAMS.HIRING) this.staff[k] = 0;
        for(let k in GAME_PARAMS.CLASSES) this.class_state[k] = { level: 1, cooldown: 0 };
        for(let k in GAME_PARAMS.FEATURES) this.features_unlocked[k] = false;
        ui.update(); ui.renderRD(); ui.renderClasses(); ui.initLogFloat();
    },

    nextMonth: function() {
        this.date.month++;
        if (this.date.month > 12) { this.date.month = 1; this.date.year++; }
        const isFreeEra = (this.date.year === 2013 && this.date.month < 7);
        ui.log(`=== ${this.date.year}年 ${this.date.month}月 ===`, 'log-warn');
        if (this.date.year >= 2026) { this.endGame(); return; }

        this.ap = 100;

        // --- Loan interest (compound) ---
        if (this.loan_balance > 0) {
            let intv = Math.ceil(this.loan_balance * GAME_PARAMS.LOAN.RATE);
            this.loan_balance += intv;
            ui.log(`贷款: 产生利息 ${intv},当前欠款 ${this.loan_balance}`, "log-warn");
        }

// --- Loan term countdown & forced repayment ---
if (this.loan_balance > 0 && this.loan_term_left > 0) {
    this.loan_term_left -= 1;
    if (this.loan_term_left === 0) {
        let due = this.loan_balance;
        ui.log(`⏰ 贷款到期:需要强制还款 ${due} 元`, "log-warn");
        if (this.money >= due) {
            this.money -= due;
            this.loan_balance = 0;
            ui.log(`✅ 已强制还清贷款 ${due} 元`, "log-good");
        } else {
            alert("贷款到期强制还款失败:余额不足。游戏结束。");
            document.getElementById('next-month-btn').disabled = true;
            return;
        }
    }
}

        let salary = 0;
        let gain = { rd: 0, acad: 0, comm: 0 };
        for (let k in this.staff) {
            let count = this.staff[k], conf = GAME_PARAMS.HIRING[k];
            if (count > 0) { salary += count * conf.cost; gain[conf.type] += count * conf.gain; }
        }
        
        this.money += GAME_PARAMS.INCOME.MONTHLY_GRANT;
        this.money -= salary;
        this.resources.rd += gain.rd; this.resources.acad += gain.acad; this.resources.comm += gain.comm;
        
        if(salary > 0) ui.log(`财务: 发放工资 ${salary}, 产出 R:${gain.rd} A:${gain.acad} C:${gain.comm}`);

        // Maintenance
        let maintain_rd = 0;
        for (let k in GAME_PARAMS.FEATURES) if (this.features_unlocked[k]) maintain_rd += GAME_PARAMS.FEATURES[k].maintain;
        maintain_rd += 20 * (this.hardware.server_arch - 1);
        maintain_rd += 20 * (this.hardware.judge_arch - 1);
        this.resources.rd -= maintain_rd;
        
        // Hardware Costs
        let hw_cost = this.hardware.disk_extra_blocks * GAME_PARAMS.COSTS.DISK_BLOCK_RENT;
        if (!isFreeEra) {
            hw_cost += this.hardware.server_cores * GAME_PARAMS.COSTS.SERVER_CORE_RENT;
            hw_cost += this.hardware.judge_cores * GAME_PARAMS.COSTS.JUDGE_CORE_RENT;
        }
        this.money -= hw_cost;
        if(hw_cost > 0) ui.log(`财务: 硬件租金 ${hw_cost}`);

        // --- Load & API ---
        let server_arch_bonus = (this.hardware.server_arch - 1) * 100;
        let judge_arch_bonus = (this.hardware.judge_arch - 1) * 100;

        let server_cap_unit = 500 - (this.features_unlocked.team?50:0) - (this.features_unlocked.api?50:0) + server_arch_bonus;
        let judge_cap_unit = 500 + judge_arch_bonus;

        let server_cap = this.hardware.server_cores * server_cap_unit;
        let judge_cap = this.hardware.judge_cores * judge_cap_unit;

        let u = this.stats.users;
        let load = u.active + (u.normal / 3) + (u.core * 3);
        if ([7,8,9,10].includes(this.date.month)) load *= 1.3;
        
        if (this.features_unlocked.api) {
            let total_u = u.normal + u.active + u.core;
            this.money += Math.floor(total_u * 1.0); 
        }

        // --- Growth Logic ---
        let rep = this.stats.reputation;
        let total_users = u.normal + u.active + u.core;
        
        let pct_rate = (rep * 8 + 4) / 100;
        if (pct_rate < 0) pct_rate = 0;
        let growth_from_pct = total_users * pct_rate;
        let growth_from_fixed = (rep + 1) * 50;
        if (growth_from_fixed < 0) growth_from_fixed = 0;

        let new_users = Math.floor(growth_from_pct + growth_from_fixed);
        if (this.flags.contest_growth_mult !== 1.0) {
            new_users = Math.floor(new_users * this.flags.contest_growth_mult);
            this.flags.contest_growth_mult = 1.0; 
        }
        
        // 新增:搬题奖励发放
        if (this.stats.pending_user_bonus > 0) {
            new_users += this.stats.pending_user_bonus;
            ui.log(`推广: 试题扩充吸引了额外 ${this.stats.pending_user_bonus} 名用户`);
            this.stats.pending_user_bonus = 0;
        }
        
        u.normal += new_users;
        if (new_users > 0) ui.log(`用户: 新增 ${new_users} 人`);

        // Flow
        let prob_n2a = (rep * GAME_PARAMS.USER_BEHAVIOR.UPGRADE_N_A_MULT + GAME_PARAMS.USER_BEHAVIOR.UPGRADE_N_A_BASE) / 100;
        if (this.features_unlocked.difficulty) prob_n2a *= 1.1;
        if (this.features_unlocked.solution) prob_n2a *= 1.25;
        if (this.features_unlocked.discuss) prob_n2a *= 1.1;
        if (this.features_unlocked.team) prob_n2a *= 1.1;
        
        let delta_n2a = Math.floor(u.normal * prob_n2a);
        if (delta_n2a > 0) { u.normal -= delta_n2a; u.active += delta_n2a; } 
        else { let delta_a2n = Math.floor(u.active * Math.abs(prob_n2a)); u.active -= delta_a2n; u.normal += delta_a2n; }

        let prob_a2c = (rep * GAME_PARAMS.USER_BEHAVIOR.UPGRADE_A_C_MULT) / 100;
        let delta_a2c = Math.floor(u.active * prob_a2c);
        if (delta_a2c > 0) { u.active -= delta_a2c; u.core += delta_a2c; }

        // Churn
        u.normal -= Math.ceil(u.normal / 120); u.active -= Math.ceil(u.active / 60);
        let core_churn = Math.ceil(u.core / 36); u.core -= core_churn; u.active += core_churn;

        // Data & Disk
        let subs = (u.normal * GAME_PARAMS.USER_BEHAVIOR.SUBMISSIONS.normal) +
                   (u.active * GAME_PARAMS.USER_BEHAVIOR.SUBMISSIONS.active) +
                   (u.core * GAME_PARAMS.USER_BEHAVIOR.SUBMISSIONS.core);
        let subs_int = Math.floor(subs);
        this.stats.total_submissions += subs_int;
        this.stats.last_month.subs = subs_int; 
        
        let sols = 0;
        if (this.features_unlocked.solution) {
            sols = (u.normal * GAME_PARAMS.USER_BEHAVIOR.SOLUTIONS.normal) +
                   (u.active * GAME_PARAMS.USER_BEHAVIOR.SOLUTIONS.active) +
                   (u.core * GAME_PARAMS.USER_BEHAVIOR.SOLUTIONS.core);
        }
        let sols_int = Math.floor(sols);
        this.stats.total_solutions += sols_int;
        this.stats.last_month.sols = sols_int;

        let p = this.stats.problems;
        let total_prob = p.high + p.mid + p.low;
        let disk = (total_prob * GAME_PARAMS.DISK_USAGE.PROBLEM) +
                   (total_users * GAME_PARAMS.DISK_USAGE.USER_DATA) +
                   (this.stats.total_submissions * GAME_PARAMS.DISK_USAGE.SUBMISSION) +
                   (this.stats.total_solutions * GAME_PARAMS.DISK_USAGE.SOLUTION);
        this.hardware.disk_used = disk;
        
        let disk_cap = GAME_PARAMS.INITIAL.DISK_MB + (this.hardware.disk_extra_blocks * GAME_PARAMS.COSTS.DISK_BLOCK_SIZE_GB * 1024);
        
        // System Failure Check
        let is_disk_full = disk > disk_cap;
        let is_server_overload = load > server_cap;
        let is_judge_overload = load > judge_cap;
        
        if (is_disk_full) ui.log("❌ 硬盘已满!数据写入失败!", "log-bad");
        if (is_server_overload) ui.log("❌ Web服务器过载!", "log-bad");
        if (is_judge_overload) ui.log("❌ 评测机过载!", "log-bad");
        
        if (is_disk_full || is_server_overload || is_judge_overload) {
            this.stats.consecutive_failure_months++;
            ui.log(`⚠️ 系统故障!连续故障月数: ${this.stats.consecutive_failure_months}/6`, "log-bad");
            if (this.stats.consecutive_failure_months >= 6) {
                alert("由于长期维护不善(连续6个月故障),您的 OJ 被视作关停。游戏结束。");
                document.getElementById('next-month-btn').disabled = true;
                return;
            }
        } else {
            if (this.stats.consecutive_failure_months > 0) {
                ui.log("✅ 系统状态已恢复正常,故障计数重置。", "log-good");
            }
            this.stats.consecutive_failure_months = 0;
        }

        // Needs & Rep
        let acad_need = 0;
        if (this.features_unlocked.difficulty) acad_need += total_prob * 0.05;
        if (this.features_unlocked.solution) acad_need += total_prob * 0.1;
        let comm_need = 0;
        if (this.features_unlocked.discuss) comm_need += total_users * 0.01;
        
        this.resources.acad -= acad_need;
        this.resources.comm -= comm_need;
        
        let acad_gap = (this.resources.acad < 0) ? Math.abs(this.resources.acad) : 0;
        let comm_gap = (this.resources.comm < 0) ? Math.abs(this.resources.comm) : 0;
        
        // 新增:社区缺口统计
        if (comm_gap > 0) this.stats.comm_fail_months++;
        // 新增:每年1月罚款逻辑
        if (this.date.month === 1 && this.stats.comm_fail_months >= 12 && total_users >= 10000) {
            let fine = total_users;
            this.money -= fine;
            ui.log(`⚖️ 监管通知: 因长期社区维护不善,罚款 ${fine} 元!`, "log-bad");
            this.stats.comm_fail_months = 0; // 罚款后重置计数
        }

        let server_gap = Math.max(0, load - server_cap);
        let judge_gap = Math.max(0, load - judge_cap);
        
        this.calculateReputation(acad_gap, comm_gap, server_gap, judge_gap, total_prob);

        // Cooldowns
        for(let k in this.class_state) if (this.class_state[k].cooldown > 0) this.class_state[k].cooldown--;
        
        if (this.flags.fundraising_penalty_val > 0) {
            this.flags.fundraising_penalty_val -= (0.2 / 12);
            if (this.flags.fundraising_penalty_val < 0) this.flags.fundraising_penalty_val = 0;
        }
        if (this.flags.temp_rep_penalty > 0) this.flags.temp_rep_penalty = Math.max(0, this.flags.temp_rep_penalty - (0.1/3));

        if (this.money < 0) { alert("资金链断裂!您破产了!"); document.getElementById('next-month-btn').disabled = true; }

        ui.update();
    },

    calculateReputation: function(acad_gap, comm_gap, server_gap, judge_gap, total_prob) {
        let p = this.stats.problems, u = this.stats.users;
        let r_acad = 0;
        if (p.high > 0) r_acad += Math.log10(p.high) / 10;
        if (total_prob > 0 && (p.high / total_prob) > 0.4) r_acad += (p.high/total_prob) - 0.4;
        if (p.low > 0) r_acad -= Math.log10(p.low) / 10;
        r_acad = Math.min(0.4, r_acad);

        let r_users = (Math.log(Math.max(1, u.normal))/Math.log(200) + Math.log(Math.max(1, u.active))/Math.log(100) + Math.log(Math.max(1, u.core))/Math.log(50)) / 40;
        r_users = Math.min(0.3, r_users);

        let r_edu = 0;
        r_edu += this.class_state.intro.level * 0.01;
        r_edu += this.class_state.basic.level * 0.01;
        r_edu += this.class_state.advanced.level * 0.02;
        r_edu += this.class_state.sprint.level * 0.02;
        r_edu += this.class_state.national.level * 0.02;
        r_edu = Math.min(0.3, r_edu);

        let r_stable = 0;
        if (acad_gap > 0) r_stable -= Math.log10(acad_gap) / 20;
        if (comm_gap > 0) r_stable -= Math.log10(comm_gap) / 20;
        if (server_gap > 0) r_stable -= Math.log10(server_gap) / 10;
        if (judge_gap > 0) r_stable -= Math.log10(judge_gap) / 10;
        
        r_stable -= this.flags.fundraising_penalty_val;
        r_stable -= this.flags.temp_rep_penalty;
        
        let total = r_acad + r_users + r_edu + r_stable;
        if (total > 1) total = 1;
        this.stats.reputation = total;
    },

    convertAP: function(target) {
        if (this.ap < 10) { ui.log("AP不足", "log-bad"); return; }
        this.ap -= 10; this.resources[target] += 10;
        ui.log(`行动: 10 AP -> 10 ${target}点`); ui.update();
    },


    convertAPAll: function(target) {
        const label = (target === 'rd') ? '研发' : (target === 'acad') ? '学术' : '社区';
        const times = Math.floor(this.ap / 10);
        if (times <= 0) { ui.log("AP不足", "log-bad"); return; }
        const amount = times * 10;
        this.ap -= amount;
        this.resources[target] += amount;
        ui.log(`行动: 一键转化 ${amount} AP -> ${amount} ${label}点`, "log-good");
        ui.update();
    },


    addProblem: function(type) {
        if (this.resources.acad < GAME_PARAMS.COSTS.ADD_PROBLEM_ACAD) { ui.log("学术不足", "log-bad"); return; }
        this.resources.acad -= GAME_PARAMS.COSTS.ADD_PROBLEM_ACAD;
        
        // 新增:搬题奖励记录
        if (type === 'mid' || type === 'low') {
            let bonus = Math.floor(Math.random() * 41) + 10; // 10-50
            this.stats.pending_user_bonus = (this.stats.pending_user_bonus || 0) + bonus;
        }

        if (type === 'high') this.stats.problems.high += 3;
        if (type === 'mid') this.stats.problems.mid += 10;
        if (type === 'low') { this.stats.problems.low += 20; this.stats.reputation -= 0.001; }
        ui.log(`题库: 增加 ${type} 试题`); ui.update();
    },

    organizeContest: function(key) {
        let conf = GAME_PARAMS.CONTESTS[key];
        if (this.money < conf.cost || this.resources.acad < conf.acad) { ui.log("资源不足", "log-bad"); return; }
        this.money -= conf.cost; this.resources.acad -= conf.acad;
        
        if (conf.prob_mid) this.stats.problems.mid += conf.prob_mid;
        if (conf.prob_low) this.stats.problems.low += conf.prob_low;
        if (conf.prob_high) this.stats.problems.high += conf.prob_high;
        
        if (conf.rep_pen) this.flags.temp_rep_penalty += conf.rep_pen;
        if (conf.rep_gain) this.stats.reputation += conf.rep_gain;
        if (conf.growth_mult) this.flags.contest_growth_mult = conf.growth_mult;
        
        ui.log(`比赛: ${conf.name} (下月增长 x${conf.growth_mult})`); ui.update();
    },

    fundraising: function() {
        if (this.ap < 50) return;
        this.ap -= 50;
        let u = this.stats.users;
        let total_users = u.normal + u.active + u.core;
        let gain = Math.floor(total_users * (this.stats.reputation + 1) * 5);
        if (gain < 0) gain = 0;
        
        this.money += gain;
        this.flags.fundraising_penalty_val += 0.2;
        ui.log(`募捐: 获得 ${gain} 元,声誉受损`, "log-bad"); ui.update();
    },


    getLoanLimit: function() {
        let u = this.stats.users;
        let total_users = u.normal + u.active + u.core;
        // 上限随用户数与声誉增长(简单模型)
        let rep_factor = 1 + Math.max(0, this.stats.reputation);
        let limit = Math.floor((GAME_PARAMS.LOAN.BASE_LIMIT + total_users * GAME_PARAMS.LOAN.PER_USER) * rep_factor);
        return Math.max(GAME_PARAMS.LOAN.BASE_LIMIT, limit);
    },

    borrowLoanCustom: function() {
        const amtEl = document.getElementById('loan-amt');
        const termEl = document.getElementById('loan-term');
        let amount = parseInt((amtEl && amtEl.value) ? amtEl.value : '0', 10);
        let term = parseInt((termEl && termEl.value) ? termEl.value : '0', 10);

        if (!Number.isFinite(amount) || amount <= 0) { ui.log("请输入有效借款金额", "log-bad"); return; }
        if (!Number.isFinite(term) || term <= 0) { ui.log("请输入有效期限(月)", "log-bad"); return; }
        term = Math.min(24, Math.max(1, term));

        if (this.ap < GAME_PARAMS.LOAN.AP_COST) { ui.log("AP不足", "log-bad"); return; }

        // 简化:同一时间只允许存在一笔贷款(避免多笔到期分摊计算过于复杂)
        if (this.loan_balance > 0 && this.loan_term_left > 0) {
            ui.log("当前已有未到期贷款,暂不支持叠加借款(请先还清或等待到期)", "log-warn");
            return;
        }

        let limit = this.getLoanLimit();
        if (amount > limit) amount = limit;

        if (amount <= 0) { ui.log("无法借款", "log-bad"); return; }

        this.ap -= GAME_PARAMS.LOAN.AP_COST;
        this.loan_balance += amount;
        this.loan_term_left = term;
        this.money += amount;

        ui.log(`贷款: 借入 ${amount} 元,期限 ${term} 月 (欠款 ${this.loan_balance})`, "log-good");
        ui.update();
    },

    repayLoanCustom: function() {
        const el = document.getElementById('repay-amt');
        let amount = parseInt((el && el.value) ? el.value : '0', 10);
        if (!Number.isFinite(amount) || amount <= 0) { ui.log("请输入有效还款金额", "log-bad"); return; }
        this.repayLoan(amount);
    },

    fire: function(key) {
        if (!this.staff[key] || this.staff[key] <= 0) { ui.log("暂无可辞退人员", "log-warn"); return; }
        if (this.ap < 30) { ui.log("AP不足(辞退需要 30 AP)", "log-bad"); return; }
        let conf = GAME_PARAMS.HIRING[key];
        let fee = Math.ceil(conf.cost * 0.5);

        if (this.money < fee) { ui.log(`资金不足,无法支付辞退补偿 ${fee}`, "log-bad"); return; }

        this.ap -= 30;
        this.money -= fee;
        this.staff[key] -= 1;

        ui.log(`辞退: ${conf.name}(补偿 ${fee} + 30AP)`, "log-warn");
        ui.update();
    },

    borrowLoan: function(amount) {
        if (this.ap < GAME_PARAMS.LOAN.AP_COST) { ui.log("AP不足", "log-bad"); return; }
        let limit = this.getLoanLimit();
        let can = limit - this.loan_balance;
        if (can <= 0) { ui.log("已达贷款上限", "log-bad"); return; }

        let add = (amount === 'max') ? can : Math.min(can, amount);
        if (add <= 0) { ui.log("无法借款", "log-bad"); return; }

        this.ap -= GAME_PARAMS.LOAN.AP_COST;
        this.loan_balance += add;
        this.money += add;
        ui.log(`贷款: 借入 ${add} 元 (欠款 ${this.loan_balance})`, "log-good");
        ui.update();
    },

    repayLoan: function(amount) {
        if (this.ap < GAME_PARAMS.LOAN.AP_COST) { ui.log("AP不足", "log-bad"); return; }
        if (this.loan_balance <= 0) { ui.log("当前无欠款", "log-warn"); return; }

        let pay = (amount === 'all') ? this.loan_balance : Math.min(this.loan_balance, amount);
        pay = Math.min(pay, this.money);
        if (pay <= 0) { ui.log("资金不足,无法还款", "log-bad"); return; }

        this.ap -= GAME_PARAMS.LOAN.AP_COST;
        this.loan_balance -= pay;
        this.money -= pay;
        if (this.loan_balance <= 0) { this.loan_balance = 0; this.loan_term_left = 0; }
        ui.log(`贷款: 偿还 ${pay} 元 (剩余欠款 ${this.loan_balance})`, "log-good");
        ui.update();
    },

    hire: function(key) {
        if (this.ap < GAME_PARAMS.COSTS.HIRE_AP) return;
        let conf = GAME_PARAMS.HIRING[key];
        if (this.staff[key] >= conf.limit) { ui.log("人数已满", "log-bad"); return; }
        this.staff[key]++; this.ap -= GAME_PARAMS.COSTS.HIRE_AP;
        ui.log(`招聘: ${conf.name}`); ui.update();
    },

    expandHardware: function(type, mode) {
        // mode: 1 (default), 10, 'max'
        mode = mode || 1;
        
        if (type === 'disk') {
            let count = (typeof mode === 'number') ? mode : 1;
            this.hardware.disk_extra_blocks += count; 
            ui.log(`硬件: 硬盘扩容 (+${count * 40}GB)`); 
            ui.update(); 
            return; 
        }

        
        let isServer = (type === 'server');
        let arch = isServer ? this.hardware.server_arch : this.hardware.judge_arch;
        let current = isServer ? this.hardware.server_cores : this.hardware.judge_cores;
        let limit_base = isServer ? 32 : 64;
        let limit = limit_base * arch;
        
        let count = 0;
        if (mode === 'max') {
            count = limit - current;
        } else {
            count = mode;
        }
        
        if (count <= 0) {
            ui.log("已达到架构上限,无法添加", "log-bad");
            return;
        }
        
        if (current + count > limit) {
            count = limit - current;
            if (count <= 0) {
                 ui.log("核心数已达架构上限,请先升级架构", "log-bad");
                 return;
            }
        }
        
        if (isServer) this.hardware.server_cores += count;
        else this.hardware.judge_cores += count;
        
        ui.log(`硬件: ${isServer?'Web':'评测'}核心 +${count}`);
        ui.update();
    },

    upgradeArch: function(type) {
        let level = (type === 'server') ? this.hardware.server_arch : this.hardware.judge_arch;
        let cost_m = 10000 * level, cost_r = 100 * level;
        if (this.money < cost_m || this.resources.rd < cost_r) { ui.log("资源不足", "log-bad"); return; }
        this.money -= cost_m; this.resources.rd -= cost_r;
        if (type === 'server') this.hardware.server_arch++; else this.hardware.judge_arch++;
        ui.log(`研发: 架构升级成功`, "log-good"); ui.update();
    },

    unlockFeature: function(id) {
        let f = GAME_PARAMS.FEATURES[id];
        if (this.money < f.cost || this.resources.rd < f.rd) { ui.log("资源不足", "log-bad"); return; }
        this.money -= f.cost; this.resources.rd -= f.rd;
        this.features_unlocked[id] = true;
        ui.log(`解锁: ${f.name}`, "log-good");
        ui.renderRD(); if(id==='school') ui.renderClasses(); ui.update();
    },

    runClass: function(id) {
        let conf = GAME_PARAMS.CLASSES[id], st = this.class_state[id];
        if (conf.season && !conf.season.includes(this.date.month)) { ui.log("季节不符 (需7-10月)", "log-bad"); return; }
        if (st.cooldown > 0) return;
        if (this.money < conf.req_money || this.resources.acad < conf.req_acad) { ui.log("无法开课 (资源不足)", "log-bad"); return; }
        
        this.money -= conf.req_money; this.resources.acad -= conf.req_acad;
        this.money += conf.inc_base * st.level; st.cooldown = 3;
        ui.log(`网校: ${conf.name} 开课`, "log-good"); ui.update();
    },

    upgradeClass: function(id) {
        let conf = GAME_PARAMS.CLASSES[id], st = this.class_state[id];
        if (st.level >= conf.max) return;
        
        let cm = conf.cost_base * st.level;
        let ca = conf.acad_base * st.level;
        
        if (this.money < cm || this.resources.acad < ca) { ui.log("升级资源不足", "log-bad"); return; }
        this.money -= cm; this.resources.acad -= ca; st.level++;
        ui.log(`网校: 升级 ${conf.name}`, "log-good"); ui.renderClasses(); ui.update();
    },

    endGame: function() {
        let u = this.stats.users, p = this.stats.problems;
        let score = (u.core/3) + (u.active/10) + (u.normal/100) + (3*p.high + p.mid - p.low) + (0.01 * this.stats.total_solutions);
        score *= (this.stats.reputation + 1);
        alert(`游戏结束!最终得分: ${score.toFixed(2)}`);
        document.getElementById('next-month-btn').disabled = true;
    }
};

const ui = {
    update: function() {
        document.getElementById('disp-date').innerText = `${game.date.year}-${String(game.date.month).padStart(2,'0')}`;
        document.getElementById('disp-money').innerText = game.money;
        document.getElementById('disp-loan').innerText = game.loan_balance;
        const loanLimitEl = document.getElementById('disp-loan-limit');
        if (loanLimitEl) loanLimitEl.innerText = game.getLoanLimit();
        const loanRateEl = document.getElementById('disp-loan-rate');
        if (loanRateEl) loanRateEl.innerText = (GAME_PARAMS.LOAN.RATE * 100).toFixed(1) + '%';
        const loanTermEl = document.getElementById('disp-loan-term');
        if (loanTermEl) loanTermEl.innerText = (game.loan_balance > 0 ? (game.loan_term_left + '月') : '—');
        document.getElementById('disp-rep').innerText = game.stats.reputation.toFixed(3);
        document.getElementById('rep-bar').style.width = Math.max(0, Math.min(100, (game.stats.reputation + 1)/2*100)) + '%';
        
        document.getElementById('disp-ap').innerText = game.ap;
        document.getElementById('disp-rd').innerText = Math.floor(game.resources.rd);
        document.getElementById('disp-acad').innerText = Math.floor(game.resources.acad);
        document.getElementById('disp-comm').innerText = Math.floor(game.resources.comm);
        
        let u = game.stats.users;
        document.getElementById('disp-users-total').innerText = u.normal + u.active + u.core;
        
        let failSpan = document.getElementById('disp-fail-months');
        failSpan.innerText = `${game.stats.consecutive_failure_months} / 6`;
        failSpan.style.color = game.stats.consecutive_failure_months > 0 ? '#ff5252' : '#aaa';

        document.getElementById('disp-last-subs').innerText = game.stats.last_month.subs.toLocaleString();
        document.getElementById('disp-last-sols').innerText = game.stats.last_month.sols.toLocaleString();

        let p = game.stats.problems;
        document.getElementById('disp-prob-high').innerText = p.high;
        document.getElementById('disp-prob-mid').innerText = p.mid;
        document.getElementById('disp-prob-low').innerText = p.low;
        
        let disk_cap = GAME_PARAMS.INITIAL.DISK_MB + (game.hardware.disk_extra_blocks * GAME_PARAMS.COSTS.DISK_BLOCK_SIZE_GB * 1024);
        document.getElementById('disp-disk').innerText = `${(game.hardware.disk_used/1024).toFixed(1)} / ${(disk_cap/1024).toFixed(1)} GB`;
        
        document.getElementById('disp-server-core').innerText = game.hardware.server_cores;
        document.getElementById('disp-server-arch').innerText = game.hardware.server_arch;
        document.getElementById('disp-judge-core').innerText = game.hardware.judge_cores;
        document.getElementById('disp-judge-arch').innerText = game.hardware.judge_arch;
        
        // Load Calculation
        let server_arch_bonus = (game.hardware.server_arch - 1) * 100;
        let server_cap_unit = 500 - (game.features_unlocked.team?50:0) - (game.features_unlocked.api?50:0) + server_arch_bonus;
        let s_cap = game.hardware.server_cores * server_cap_unit;
        
        // --- 新增开始 ---
        let judge_arch_bonus = (game.hardware.judge_arch - 1) * 100;
        let judge_cap_unit = 500 + judge_arch_bonus;
        let j_cap = game.hardware.judge_cores * judge_cap_unit;
        // --- 新增结束 ---

        let load = Math.floor(u.active + (u.normal/3) + (u.core*3));
        if ([7,8,9,10].includes(game.date.month)) load = Math.floor(load * 1.3);
        
        document.getElementById('disp-server-load').innerText = load;
        document.getElementById('disp-server-cap').innerText = s_cap;
        document.getElementById('disp-server-load').style.color = (load > s_cap) ? '#ff5252' : '#888'; // 简单的红字警告
        
        // --- 新增开始 ---
        document.getElementById('disp-judge-load').innerText = load;
        document.getElementById('disp-judge-cap').innerText = j_cap;
        document.getElementById('disp-judge-load').style.color = (load > j_cap) ? '#ff5252' : '#888'; // 简单的红字警告
        // --- 新增结束 ---


        // Forecast
        let salary = 0;
        for (let k in game.staff) salary += game.staff[k] * GAME_PARAMS.HIRING[k].cost;
        
        let hw_cost = game.hardware.disk_extra_blocks * GAME_PARAMS.COSTS.DISK_BLOCK_RENT;
        let next_month = game.date.month + 1, next_year = game.date.year;
        if (next_month > 12) { next_month = 1; next_year++; }
        let will_charge_hw = (next_year > 2013) || (next_year === 2013 && next_month >= 7);
        if (will_charge_hw) {
            hw_cost += game.hardware.server_cores * GAME_PARAMS.COSTS.SERVER_CORE_RENT;
            hw_cost += game.hardware.judge_cores * GAME_PARAMS.COSTS.JUDGE_CORE_RENT;
        }
        let loan_int = Math.ceil(game.loan_balance * GAME_PARAMS.LOAN.RATE);
        let total_expense = salary + hw_cost + loan_int;
        
        let api_income = 0;
        if (game.features_unlocked.api) api_income = Math.floor((u.normal+u.active+u.core) * 1.0); // Now 1.0
        let total_income = GAME_PARAMS.INCOME.MONTHLY_GRANT + api_income;
        
        let m_rd = 0, m_acad = 0, m_comm = 0;
        for (let k in GAME_PARAMS.FEATURES) if (game.features_unlocked[k]) m_rd += GAME_PARAMS.FEATURES[k].maintain;
        m_rd += 20 * (game.hardware.server_arch - 1) + 20 * (game.hardware.judge_arch - 1);
        let total_prob = p.high + p.mid + p.low;
        if (game.features_unlocked.difficulty) m_acad += total_prob * 0.05;
        if (game.features_unlocked.solution) m_acad += total_prob * 0.1;
        if (game.features_unlocked.discuss) m_comm += (u.normal+u.active+u.core) * 0.01;

        document.getElementById('fc-income').innerText = `+${total_income}`;
        document.getElementById('fc-salary').innerText = `-${salary}`;
        document.getElementById('fc-maint').innerText = `-${hw_cost}`;
        document.getElementById('fc-loan-int').innerText = `-${loan_int}`;
        
        let net = total_income - total_expense;
        let netEl = document.getElementById('fc-net');
        netEl.innerText = (net >= 0 ? '+' : '') + net;
        netEl.style.color = (net >= 0 ? '#69f0ae' : '#ff5252');
        
        document.getElementById('fc-rd-cost').innerText = m_rd;
        document.getElementById('fc-acad-cost').innerText = Math.floor(m_acad);
        document.getElementById('fc-comm-cost').innerText = Math.floor(m_comm);

        let banner = document.getElementById('warning-banner');
        banner.style.display = (game.money + net < 0) ? 'block' : 'none';
        if (game.money + net < 0) banner.innerText = `⚠️ 严重警告:按当前收支,下个月将破产 (预计余额 ${game.money + net})!`;

        let staff = game.staff, h = GAME_PARAMS.HIRING;
        document.getElementById('out-rd').innerText = staff.rd_student*h.rd_student.gain + staff.rd_uni*h.rd_uni.gain + staff.rd_full*h.rd_full.gain;
        document.getElementById('out-acad').innerText = staff.acad_part*h.acad_part.gain + staff.acad_full*h.acad_full.gain;
        document.getElementById('out-comm').innerText = staff.comm_part*h.comm_part.gain + staff.comm_full*h.comm_full.gain;
        // 更新架构升级费用显示
        let s_lvl = game.hardware.server_arch;
        let j_lvl = game.hardware.judge_arch;
        document.getElementById('disp-up-server-cost').innerText = `$${10000*s_lvl} + ${100*s_lvl} 研发`;
        document.getElementById('disp-up-judge-cost').innerText = `$${10000*j_lvl} + ${100*j_lvl} 研发`;

        ui.renderClasses(); // Refresh edu tab for cooldown/level text
    },
    
    log: function(msg, cls) {
        let div = document.createElement('div');
        div.className = 'log-entry ' + (cls||'');
        div.innerText = `[${game.date.year}-${game.date.month}] ` + msg;
        let area = document.getElementById('log-area');
        area.appendChild(div);
        area.scrollTop = area.scrollHeight;
    },
    
    switchTab: function(id) {
        document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
        event.target.classList.add('active');
        document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
        document.getElementById('tab-'+id).classList.add('active');
    },

    toggleLog: function(force) {
        const w = document.getElementById('log-float');
        if (!w) return;
        const shouldShow = (typeof force === 'boolean') ? force : !w.classList.contains('show');
        w.classList.toggle('show', shouldShow);
    },

    clearLog: function() {
        const area = document.getElementById('log-area');
        if (!area) return;
        area.innerHTML = '';
        ui.log('日志已清空', 'log-warn');
    },

    initLogFloat: function() {
        const w = document.getElementById('log-float');
        const header = document.getElementById('log-float-header');
        if (!w || !header) return;

        let dragging = false;
        let startX = 0, startY = 0, startLeft = 0, startTop = 0;

        const onMove = (e) => {
            if (!dragging) return;
            const dx = (e.clientX - startX);
            const dy = (e.clientY - startY);
            w.style.left = (startLeft + dx) + 'px';
            w.style.top  = (startTop + dy) + 'px';
            w.style.right = 'auto';
            w.style.bottom = 'auto';
        };

        const onUp = () => {
            dragging = false;
            document.removeEventListener('mousemove', onMove);
            document.removeEventListener('mouseup', onUp);
        };

        header.addEventListener('mousedown', (e) => {
            // prevent drag when clicking header buttons
            if (e.target && e.target.tagName === 'BUTTON') return;
            dragging = true;
            const rect = w.getBoundingClientRect();
            startX = e.clientX; startY = e.clientY;
            startLeft = rect.left; startTop = rect.top;
            document.addEventListener('mousemove', onMove);
            document.addEventListener('mouseup', onUp);
        });
    },

    renderRD: function() {
        let html = '';
        for (let k in GAME_PARAMS.FEATURES) {
            let f = GAME_PARAMS.FEATURES[k], isUn = game.features_unlocked[k];
            let btn = isUn ? `<button disabled>✅ 已完成</button>` : `<button onclick="game.unlockFeature('${k}')">研发 ($${f.cost}, 研发点 ${f.rd})</button>`;
            html += `<div style="
    background: var(--panel-bg);
    padding: 14px;
    margin-bottom: 12px;
    border-radius: 14px;
    border: 1px solid var(--border);
    box-shadow: var(--shadow);
    border-left: 4px solid ${isUn ? 'var(--success)' : 'var(--accent)'}
">
    <div style="display:flex; justify-content:space-between; align-items:center; gap:10px">
        <span style="font-weight:800; color: var(--text)">${f.name}</span>
        ${btn}
    </div>
    <div style="font-size:0.9em; color: var(--muted); margin-top:8px; line-height:1.45">
        ${f.desc} <span style="font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;">(维护: ${f.maintain}/月)</span>
    </div>
</div>`;
        }
        document.getElementById('rd-list').innerHTML = html;
    },
    
    renderClasses: function() {
        if (!game.features_unlocked.school) { document.getElementById('edu-list').innerHTML = `<div style="color:#888;text-align:center">请先解锁网校功能</div>`; return; }
        let html = '';
        for(let k in GAME_PARAMS.CLASSES) {
            let c = GAME_PARAMS.CLASSES[k], st = game.class_state[k], isMax = st.level >= c.max;
            
            // Run Cost text
            let runCostText = `$${c.req_money} + ${c.req_acad} 学术`;
            // Upgrade Cost text
            let upCostText = isMax ? "MAX" : `$${c.cost_base*st.level} + ${c.acad_base*st.level} 学术`;
            
            let runBtn = (st.cooldown > 0) ? `<button class="btn-run" disabled>冷却 (${st.cooldown}月)</button>` : `<button class="btn-run" onclick="game.runClass('${k}')">开课 (+${c.inc_base*st.level}元)</button>`;
            let upBtn = isMax ? `<button class="btn-up" disabled>已满级</button>` : `<button class="btn-up" onclick="game.upgradeClass('${k}')">升级</button>`;
            
            html += `
            <div class="edu-card">
                <div class="edu-header">
                    <span>${c.name} <span style="color:#ffb74d">Lv.${st.level}</span></span>
                    <span style="font-size:0.8em; color:#888">Max: Lv.${c.max}</span>
                </div>
                <div class="edu-cost">
                    <div>开课成本: ${runCostText}</div>
                    <div>升级成本: ${upCostText}</div>
                </div>
                <div class="edu-actions">
                    ${runBtn}
                    ${upBtn}
                </div>
            </div>`;
        }
        document.getElementById('edu-list').innerHTML = html;
    }
};

game.init();
</script>
</body>
</html>

提示:
  1. 以前版本都是在机房自测的版本。
  2. 此版本未获作者同意

广告

如果你觉得这篇文章很好,请给我一个关注,谢谢。
作弊器Linck
欢迎大家私信我想要添加的其他功能。

评论

0 条评论,欢迎与作者交流。

正在加载评论...