专栏文章
《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;">
⚠️ 注意:当核心数达到架构上限(32或64 × 架构等级)时,必须先升级架构才能继续购买。
</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>
提示:
- 以前版本都是在机房自测的版本。
- 此版本未获作者同意
广告
如果你觉得这篇文章很好,请给我一个关注,谢谢。
作弊器Linck
欢迎大家私信我想要添加的其他功能。
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...