Files
report_viewer/demo2.html
2025-12-16 12:27:12 +08:00

696 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>天选打工人 - 系统对战Demo</title>
<style>
:root {
--primary: #00ff9d; /* 赛博绿 */
--danger: #ff0055; /* 故障红 */
--bg: #0a0a0a;
--card-bg: #1a1a1a;
--text: #e0e0e0;
}
body {
font-family: "Consolas", "Courier New", monospace;
background-color: var(--bg);
color: var(--text);
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* --- 顶部:敌人区域 --- */
#enemy-area {
flex: 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-bottom: 1px solid #333;
position: relative;
}
.avatar {
width: 100px;
height: 100px;
background: #333;
border: 2px solid var(--danger);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
margin-bottom: 10px;
transition: transform 0.1s;
}
.hp-bar-container {
width: 200px;
height: 20px;
background: #333;
border: 1px solid #555;
position: relative;
}
.hp-bar-fill {
height: 100%;
background: var(--danger);
width: 100%;
transition: width 0.3s ease-out;
}
.intent-bubble {
margin-top: 10px;
background: #222;
padding: 5px 10px;
border-radius: 10px;
border: 1px solid var(--danger);
color: var(--danger);
font-size: 12px;
}
/* --- 中间:战斗日志 (LLM文字流) --- */
#battle-log {
flex: 3;
overflow-y: auto;
padding: 20px;
background: rgba(0, 20, 10, 0.5);
border-bottom: 1px solid #333;
font-size: 14px;
line-height: 1.6;
}
.log-entry {
margin-bottom: 8px;
opacity: 0.8;
}
.log-system {
color: var(--primary);
font-weight: bold;
}
.log-player {
color: #fff;
}
.log-enemy {
color: var(--danger);
}
.log-desc {
color: #888;
font-style: italic;
font-size: 0.9em;
margin-left: 10px;
display: block;
}
/* --- 底部:玩家区域 --- */
#player-area {
flex: 3;
display: flex;
flex-direction: column;
background: #111;
padding: 10px;
}
#stats-bar {
display: flex;
justify-content: space-around;
padding: 10px;
background: #000;
border: 1px solid #333;
margin-bottom: 10px;
}
.stat-item {
display: flex;
align-items: center;
gap: 5px;
}
.icon {
font-size: 1.2em;
}
/* --- 卡牌区域 --- */
#hand-container {
display: flex;
justify-content: center;
gap: 10px;
flex-grow: 1;
align-items: center;
}
.card {
width: 120px;
height: 160px;
background: var(--card-bg);
border: 1px solid var(--primary);
border-radius: 8px;
padding: 10px;
display: flex;
flex-direction: column;
justify-content: space-between;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
user-select: none;
position: relative;
}
.card:hover {
transform: translateY(-20px) scale(1.05);
box-shadow: 0 0 15px rgba(0, 255, 157, 0.3);
z-index: 10;
}
.card-cost {
background: var(--primary);
color: #000;
width: 25px;
height: 25px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
}
.card-name {
font-weight: bold;
color: var(--primary);
font-size: 14px;
text-align: center;
margin-top: 5px;
}
.card-desc {
font-size: 11px;
color: #aaa;
text-align: center;
}
.card-type-attack {
border-color: #ff4444;
}
.card-type-skill {
border-color: #4488ff;
}
.card-type-money {
border-color: #ffd700;
box-shadow: 0 0 5px rgba(255, 215, 0, 0.2);
}
/* 按钮 */
#end-turn-btn {
position: absolute;
right: 20px;
bottom: 100px;
padding: 15px 30px;
background: #333;
color: white;
border: 1px solid #666;
cursor: pointer;
font-family: inherit;
font-weight: bold;
}
#end-turn-btn:hover {
background: #444;
}
/* 动画特效 */
@keyframes shake {
0% {
transform: translate(1px, 1px) rotate(0deg);
}
10% {
transform: translate(-1px, -2px) rotate(-1deg);
}
20% {
transform: translate(-3px, 0px) rotate(1deg);
}
30% {
transform: translate(3px, 2px) rotate(0deg);
}
40% {
transform: translate(1px, -1px) rotate(1deg);
}
50% {
transform: translate(-1px, 2px) rotate(-1deg);
}
60% {
transform: translate(-3px, 1px) rotate(0deg);
}
70% {
transform: translate(3px, 1px) rotate(-1deg);
}
80% {
transform: translate(-1px, -1px) rotate(1deg);
}
90% {
transform: translate(1px, 2px) rotate(0deg);
}
100% {
transform: translate(1px, -2px) rotate(-1deg);
}
}
.shake-anim {
animation: shake 0.5s;
}
.damage-text {
position: absolute;
color: red;
font-size: 30px;
font-weight: bold;
animation: floatUp 1s forwards;
}
@keyframes floatUp {
0% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(-50px);
opacity: 0;
}
}
/* 状态提示 */
#game-over {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 100;
}
#game-over h1 {
color: var(--primary);
font-size: 40px;
}
</style>
</head>
<body>
<div id="enemy-area">
<div class="avatar" id="enemy-avatar">🤡</div>
<h3 id="enemy-name">势利眼班长</h3>
<div class="hp-bar-container">
<div class="hp-bar-fill" id="enemy-hp-bar"></div>
</div>
<div style="margin-top: 5px; font-size: 12px">
面子 (HP): <span id="enemy-hp-text">80</span>/80
</div>
<div class="intent-bubble" id="enemy-intent">
💬 正在酝酿一句很难听的话 (10点伤害)
</div>
</div>
<div id="battle-log">
<div class="log-entry log-system">> 系统初始化完成...</div>
<div class="log-entry log-system">> 目标锁定:[势利眼班长]</div>
<div class="log-entry log-system">> 任务:让他当众出丑。</div>
</div>
<div id="player-area">
<div id="stats-bar">
<div class="stat-item" style="color: #ff4444">
<span class="icon">❤️</span> 面子:
<span id="player-hp">100</span>
</div>
<div class="stat-item" style="color: #4488ff">
<span class="icon">🛡️</span> 厚脸皮:
<span id="player-block">0</span>
</div>
<div class="stat-item" style="color: #00ff9d">
<span class="icon"></span> 精力:
<span id="player-energy">3</span>/3
</div>
<div class="stat-item" style="color: #ffd700">
<span class="icon">💰</span> 余额: ¥<span id="player-money"
>5000</span
>
</div>
</div>
<div id="hand-container"></div>
</div>
<button id="end-turn-btn" onclick="endTurn()">结束回合</button>
<div id="game-over">
<h1 id="game-result">任务完成</h1>
<button
onclick="location.reload()"
style="padding: 10px 20px; cursor: pointer"
>
重新开始
</button>
</div>
<script>
/* --- 1. 游戏数据设定 (配置表) --- */
const GAME_STATE = {
player: {
hp: 100,
maxHp: 100,
block: 0,
energy: 3,
maxEnergy: 3,
money: 5000,
},
enemy: {
name: "势利眼班长",
hp: 80,
maxHp: 80,
nextDmg: 10,
avatar: "🤡",
},
hand: [],
deck: [],
turn: 1,
isGameOver: false,
};
// 模拟 LLM 的文案库 (实际开发中这里接 API)
const LLM_TEXTS = {
start: [
"班长看了看你的衣服,露出了鄙夷的眼神。",
"系统检测到装逼场力波动,请宿主做好准备。",
],
atk_normal: [
"你冷冷一笑:'这就是你的实力?'",
"你指出了他话里的逻辑漏洞,全场鸦雀无声。",
"你说:'去年你借我的五百块还没还呢。'",
],
atk_money: [
"你打开手机银行,语音播报:'到账一百万元'。",
"你随手掏出一把车钥匙扔在桌上:'挪下车。'",
"你用钱扇了扇风:'这屋里穷酸气太重。'",
],
defend: [
"你假装在回消息,完全无视了他的废话。",
"你使用了【左耳进右耳出】,他的嘲讽无效。",
"你微笑着看着他,就像看着一只猴子。",
],
enemy_atk: [
"班长嘲笑道:'听说你还在租房住?'",
"班长向大家展示他的劳力士绿水鬼。",
"班长说:'服务员,给这位加把椅子,别让他站着。'",
],
};
// 卡牌数据库 (原型)
const CARD_DATABASE = [
{
id: "atk_1",
name: "阴阳怪气",
type: "attack",
cost: 1,
val: 8,
desc: "造成 8 点面子伤害",
flavorType: "atk_normal",
},
{
id: "atk_2",
name: "揭露黑历史",
type: "attack",
cost: 2,
val: 18,
desc: "造成 18 点面子伤害",
flavorType: "atk_normal",
},
{
id: "def_1",
name: "装聋作哑",
type: "skill",
cost: 1,
val: 8,
desc: "获得 8 点厚脸皮(护盾)",
flavorType: "defend",
},
{
id: "money_1",
name: "钞能力打击",
type: "money",
cost: 1,
val: 25,
moneyCost: 500,
desc: "消耗¥500造成 25 点伤害",
flavorType: "atk_money",
},
{
id: "def_2",
name: "战术喝水",
type: "skill",
cost: 0,
val: 4,
desc: "获得 4 点厚脸皮(0费)",
flavorType: "defend",
},
];
/* --- 2. 核心逻辑函数 --- */
function initGame() {
updateUI();
startPlayerTurn();
log(getRandomText(LLM_TEXTS.start), "system");
}
function startPlayerTurn() {
GAME_STATE.player.energy = GAME_STATE.player.maxEnergy;
GAME_STATE.player.block = 0; // 回合开始护盾清零(简化规则)
// 简单的抽牌逻辑:随机抽 4 张
GAME_STATE.hand = [];
for (let i = 0; i < 4; i++) {
const randomCard =
CARD_DATABASE[
Math.floor(Math.random() * CARD_DATABASE.length)
];
// 深拷贝以防止修改原数据
GAME_STATE.hand.push({
...randomCard,
uid: Date.now() + i,
});
}
updateUI();
log(`=== 第 ${GAME_STATE.turn} 回合 ===`, "system");
}
function playCard(index) {
if (GAME_STATE.isGameOver) return;
const card = GAME_STATE.hand[index];
const player = GAME_STATE.player;
// 1. 检查资源
if (player.energy < card.cost) {
log("精力不足!", "system");
shakeScreen("stats-bar");
return;
}
if (card.type === "money" && player.money < card.moneyCost) {
log("余额不足!无法使用钞能力!", "system");
return;
}
// 2. 扣除资源
player.energy -= card.cost;
if (card.type === "money") player.money -= card.moneyCost;
// 3. 执行效果
let logMsg = "";
if (card.type === "attack" || card.type === "money") {
dealDamage(card.val);
logMsg = getRandomText(LLM_TEXTS[card.flavorType]);
} else if (card.type === "skill") {
player.block += card.val;
logMsg = getRandomText(LLM_TEXTS[card.flavorType]);
}
// 4. 移除手牌 & 更新UI
GAME_STATE.hand.splice(index, 1);
log(`你使用了【${card.name}`, "player");
log(logMsg, "desc");
updateUI();
checkWin();
}
function dealDamage(amount) {
GAME_STATE.enemy.hp -= amount;
if (GAME_STATE.enemy.hp < 0) GAME_STATE.enemy.hp = 0;
// 视觉特效
const enemyEl = document.getElementById("enemy-avatar");
enemyEl.classList.add("shake-anim");
setTimeout(() => enemyEl.classList.remove("shake-anim"), 500);
// 飘字
showFloatingText(`-${amount}`);
}
function endTurn() {
if (GAME_STATE.isGameOver) return;
// 敌人行动
setTimeout(() => {
enemyAction();
}, 500);
}
function enemyAction() {
const dmg = GAME_STATE.enemy.nextDmg;
const player = GAME_STATE.player;
// 扣除护盾逻辑
let actualDmg = dmg;
if (player.block >= actualDmg) {
player.block -= actualDmg;
actualDmg = 0;
} else {
actualDmg -= player.block;
player.block = 0;
}
player.hp -= actualDmg;
log(getRandomText(LLM_TEXTS.enemy_atk), "enemy");
if (actualDmg > 0) {
log(`> 受到 ${actualDmg} 点精神伤害!`, "enemy");
document.body.classList.add("shake-anim"); // 全屏震动
setTimeout(
() => document.body.classList.remove("shake-anim"),
500
);
} else {
log(`> 你的厚脸皮完全抵挡了伤害!`, "system");
}
// 随机生成下回合意图
GAME_STATE.enemy.nextDmg = Math.floor(Math.random() * 10) + 5;
GAME_STATE.turn++;
updateUI();
checkLoss();
if (!GAME_STATE.isGameOver) {
setTimeout(startPlayerTurn, 1000);
}
}
/* --- 3. 辅助函数 --- */
function checkWin() {
if (GAME_STATE.enemy.hp <= 0) {
GAME_STATE.isGameOver = true;
document.getElementById("game-result").innerText =
"打脸成功!奖励 ¥10000";
document.getElementById("game-result").style.color =
"#00ff9d";
document.getElementById("game-over").style.display = "flex";
}
}
function checkLoss() {
if (GAME_STATE.player.hp <= 0) {
GAME_STATE.isGameOver = true;
document.getElementById("game-result").innerText =
"任务失败:你社死了";
document.getElementById("game-result").style.color =
"#ff0055";
document.getElementById("game-over").style.display = "flex";
}
}
function updateUI() {
// 敌人 UI
const hpPercent =
(GAME_STATE.enemy.hp / GAME_STATE.enemy.maxHp) * 100;
document.getElementById(
"enemy-hp-bar"
).style.width = `${hpPercent}%`;
document.getElementById("enemy-hp-text").innerText =
GAME_STATE.enemy.hp;
document.getElementById(
"enemy-intent"
).innerText = `💬 准备造成 ${GAME_STATE.enemy.nextDmg} 点伤害`;
// 玩家 UI
document.getElementById("player-hp").innerText =
GAME_STATE.player.hp;
document.getElementById("player-block").innerText =
GAME_STATE.player.block;
document.getElementById("player-energy").innerText =
GAME_STATE.player.energy;
document.getElementById("player-money").innerText =
GAME_STATE.player.money;
// 渲染手牌
const handContainer = document.getElementById("hand-container");
handContainer.innerHTML = "";
GAME_STATE.hand.forEach((card, index) => {
const cardEl = document.createElement("div");
cardEl.className = `card card-type-${card.type}`;
cardEl.onclick = () => playCard(index);
cardEl.innerHTML = `
<div class="card-cost">${card.cost}</div>
<div class="card-name">${card.name}</div>
<div class="card-desc">${card.desc}</div>
`;
handContainer.appendChild(cardEl);
});
}
function log(text, type) {
const logArea = document.getElementById("battle-log");
const entry = document.createElement("div");
entry.className = `log-entry log-${type}`;
entry.innerText = text;
logArea.appendChild(entry);
logArea.scrollTop = logArea.scrollHeight;
}
function getRandomText(array) {
return array[Math.floor(Math.random() * array.length)];
}
function showFloatingText(text) {
const enemyArea = document.getElementById("enemy-area");
const el = document.createElement("div");
el.className = "damage-text";
el.innerText = text;
el.style.left = "50%";
el.style.top = "50%";
enemyArea.appendChild(el);
setTimeout(() => el.remove(), 1000);
}
function shakeScreen(elementId) {
const el = document.getElementById(elementId);
el.classList.add("shake-anim");
setTimeout(() => el.classList.remove("shake-anim"), 500);
}
// 启动游戏
initGame();
</script>
</body>
</html>