Files
report_viewer/demo2.html

696 lines
23 KiB
HTML
Raw Normal View History

2025-12-16 12:27:12 +08:00
<!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>