<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>🤖 AI大乱斗 3.0 - 智能战场革命</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0f1a;
color: #cdd;
font-family: 'Segoe UI', sans-serif;
text-align: center;
padding: 20px;
}
h1 {
font-size: 2.8em;
margin: 15px 0;
color: #aaccff;
text-shadow: 0 0 20px rgba(100, 180, 255, 0.6);
}
.intro {
color: #9ab;
max-width: 900px;
margin: 0 auto 20px;
font-size: 1.1em;
}
/* 配置面板 */
.config-panel {
width: 90%;
max-width: 1000px;
margin: 20px auto;
padding: 15px;
background: rgba(30, 40, 60, 0.6);
border: 1px solid #335;
border-radius: 10px;
text-align: left;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
gap: 15px;
}
.char-config {
padding: 15px;
background: rgba(20, 30, 50, 0.5);
border-radius: 8px;
border: 1px solid #446;
}
.char-config h3 {
color: #bbd;
margin-bottom: 10px;
}
.slider-group {
margin: 8px 0;
display: flex;
align-items: center;
}
.slider-group label {
width: 60px;
text-align: right;
margin-right: 10px;
font-size: 0.95em;
}
input[type="range"] {
flex: 1;
}
span.value {
width: 40px;
text-align: center;
font-weight: bold;
color: #8ac;
}
.points-left {
font-size: 0.95em;
color: #faa;
margin: 5px 0;
font-weight: bold;
}
button.start {
background: linear-gradient(to bottom, #4a6fa5, #3a5a80);
color: white;
border: none;
padding: 14px 24px;
font-size: 1.1em;
font-weight: bold;
cursor: pointer;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
button.reset {
background: linear-gradient(to bottom, #8a4f4f, #7a3f3f);
color: white;
border: none;
padding: 12px 20px;
font-size: 1em;
margin: 10px 5px;
cursor: pointer;
border-radius: 6px;
}
/* 决斗场 */
#arena {
width: 90vw;
height: 60vh;
max-width: 1000px;
margin: 20px auto;
border: 2px solid #335577;
position: relative;
background: radial-gradient(circle at center, #121828 0%, #0a0f1a 80%);
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.8),
0 0 20px rgba(60, 100, 180, 0.2);
overflow: hidden;
border-radius: 12px;
}
.character {
position: absolute;
width: 56px;
height: 56px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 1.1em;
color: white;
text-shadow: 1px 1px 2px black;
user-select: none;
transition: transform 0.1s ease;
box-shadow: 0 0 10px currentColor;
z-index: 2;
}
.hp-bar-outer {
position: absolute;
top: -18px;
left: 0;
width: 56px;
height: 6px;
background: rgba(0, 0, 0, 0.6);
border-radius: 3px;
overflow: hidden;
}
.hp-bar-inner {
height: 100%;
width: 100%;
background: linear-gradient(90deg, #50c878, #41a05a);
transition: width 0.3s ease;
}
.status-tag {
position: absolute;
bottom: -18px;
left: 0;
width: 56px;
font-size: 0.7em;
text-align: center;
white-space: nowrap;
color: #ddd;
text-shadow: 1px 1px 1px black;
font-weight: bold;
}
.buff-point {
position: absolute;
width: 30px;
height: 30px;
border: 2px solid;
border-radius: 50%;
animation: pulse 1.5s infinite alternate;
z-index: 1;
box-shadow: 0 0 10px currentColor;
}
.buff-health { background: #00ff88; border-color: #00ff88; }
.buff-attack { background: #ff4444; border-color: #ff4444; }
.buff-speed { background: #44aaff; border-color: #44aaff; }
@keyframes pulse {
0% { transform: scale(1); opacity: 0.8; }
100% { transform: scale(1.2); opacity: 1; }
}
.explosion {
position: absolute;
width: 80px;
height: 80px;
background: radial-gradient(circle, #ffd700, #ff4500, transparent);
border-radius: 50%;
animation: explode 0.6s ease-out forwards;
pointer-events: none;
z-index: 10;
}
@keyframes explode {
0% { transform: scale(0); opacity: 1; }
100% { transform: scale(1.5); opacity: 0; }
}
#log {
width: 90%;
max-width: 1000px;
max-height: 140px;
margin: 15px auto;
padding: 12px;
background: rgba(20, 30, 40, 0.6);
border: 1px solid #335;
color: #ccc;
font-family: 'Courier New', monospace;
font-size: 0.95em;
overflow-y: auto;
text-align: left;
white-space: pre-line;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
}
#stats {
width: 90%;
max-width: 1000px;
margin: 15px auto;
padding: 12px;
background: rgba(20, 30, 40, 0.6);
border: 1px solid #335;
color: #ccc;
font-size: 0.95em;
border-radius: 8px;
text-align: left;
}
.controls {
margin: 20px auto;
max-width: 800px;
display: flex;
justify-content: center;
gap: 12px;
}
</style>
</head>
<body>
<h1>🤖 AI大乱斗 3.0 - 智能战场革命</h1>
<p class="intro">无障·全自动·多策略·防卡死·可视化。AI会思考、会逃跑、会抢包、会走位!</p>
<!-- 配置系统 -->
<div class="config-panel">
<script>
const chars = [
{ name: "董", color: "#e57373", hp: 50, atk: 30, spd: 20 },
{ name: "邱", color: "#64b5f6", hp: 50, atk: 30, spd: 20 },
{ name: "郭", color: "#ffb74d", hp: 50, atk: 30, spd: 20 },
{ name: "陆", color: "#ba68c8", hp: 50, atk: 30, spd: 20 }
];
function createConfig() {
const panel = document.querySelector('.config-panel');
panel.innerHTML = '';
chars.forEach(char => {
const div = document.createElement('div');
div.className = 'char-config';
div.innerHTML = `
<h3><span style="color:${char.color}">${char.name}</span> - 分配100技能点</h3>
<div class="points-left" id="points-${char.name}">剩余: 0</div>
<div class="slider-group">
<label>血量</label>
<input type="range" min="30" max="100" value="${char.hp}" step="1"
oninput="updateStat('${char.name}', 'hp', this.value)">
<span id="hp-${char.name}" class="value">${char.hp}</span>
</div>
<div class="slider-group">
<label>攻击</label>
<input type="range" min="10" max="60" value="${char.atk}" step="1"
oninput="updateStat('${char.name}', 'atk', this.value)">
<span id="atk-${char.name}" class="value">${char.atk}</span>
</div>
<div class="slider-group">
<label>速度</label>
<input type="range" min="5" max="40" value="${char.spd}" step="1"
oninput="updateStat('${char.name}', 'spd', this.value)">
<span id="spd-${char.name}" class="value">${char.spd}</span>
</div>
`;
panel.appendChild(div);
});
updateAllPoints();
}
function updateStat(name, stat, value) {
const c = chars.find(x => x.name === name);
const oldTotal = c.hp + c.atk + c.spd;
c[stat] = parseInt(value);
const newTotal = c.hp + c.atk + c.spd;
if (newTotal > 100) {
c[stat] -= (newTotal - 100);
document.querySelector(`[oninput*="'${name}', '${stat}'"]`).value = c[stat];
}
document.getElementById(`${stat}-${name}`).textContent = c[stat];
updateAllPoints();
}
function updateAllPoints() {
chars.forEach(c => {
const total = c.hp + c.atk + c.spd;
const el = document.getElementById(`points-${c.name}`);
el.textContent = `剩余: ${100 - total}`;
el.style.color = total === 100 ? '#8f8' : total < 100 ? '#faa' : '#f88';
});
}
window.onload = createConfig;
</script>
</div>
<div class="controls">
<button class="start" onclick="startGame()">▶️ 开启智能战场</button>
<button class="reset" onclick="location.reload()">🔄 重置配置</button>
</div>
<!-- 决斗场 -->
<div id="arena"></div>
<div id="log">【系统】请为每位AI分配100技能点,然后点击开始。</div>
<div id="stats">🏆 胜率统计:<br>董: 0胜|邱: 0胜|郭: 0胜|陆: 0胜</div>
<!-- 主逻辑 -->
<script>
let arena = document.getElementById("arena");
let log = document.getElementById("log");
let statsEl = document.getElementById("stats");
let gameRunning = false;
let characters = {};
let buffPoints = [];
let winCount = { 董: 0, 邱: 0, 郭: 0, 陆: 0 };
// 属性成长(非线性)
function calcHp(raw) { return 50 + Math.floor(50 * (raw / 100) ** 0.8) * 2; }
function calcAtk(raw) { return 10 + Math.floor(50 * (raw / 100) ** 0.7); }
function calcSpd(raw) { return 1.0 + parseFloat(((raw / 100) ** 0.6 * 3.0).toFixed(2)); }
function resetArena() {
cancelAnimationFrame(window.gameLoopId);
arena.innerHTML = "";
characters = {};
buffPoints = [];
gameRunning = false;
}
function logMessage(msg) {
log.innerHTML += msg + "\n";
log.scrollTop = log.scrollHeight;
}
function distance(a, b) {
return Math.hypot(a.x - b.x, a.y - b.y);
}
function spawnBuffPoint(type = 'health') {
const x = 50 + Math.random() * (arena.clientWidth - 100);
const y = 50 + Math.random() * (arena.clientHeight - 100);
const el = document.createElement("div");
el.className = `buff-point buff-${type}`;
el.dataset.type = type;
el.style.left = `${x - 15}px`;
el.style.top = `${y - 15}px`;
arena.appendChild(el);
const point = { x, y, el, type };
buffPoints.push(point);
setTimeout(() => {
if (el.parentElement) el.remove();
buffPoints = buffPoints.filter(p => p !== point);
}, 8000);
}
function findNearestBuff(character) {
return buffPoints.reduce((closest, p) => {
const d1 = closest ? distance(character, closest) : Infinity;
const d2 = distance(character, p);
return d2 < d1 ? p : closest;
}, null);
}
function createCharacter(config) {
let x = 100 + Math.random() * (arena.clientWidth - 200);
let y = 100 + Math.random() * (arena.clientHeight - 200);
const finalHp = calcHp(config.hp);
const finalAtk = calcAtk(config.atk);
const finalSpd = calcSpd(config.spd);
const el = document.createElement("div");
el.className = "character";
el.innerHTML = `
<div>${config.name}</div>
<div class="hp-bar-outer"><div class="hp-bar-inner" id="hp-ui-${config.name}"></div></div>
<div class="status-tag" id="tag-${config.name}">待命</div>
`;
el.style.backgroundColor = config.color;
el.id = "char-" + config.name;
el.style.transform = `translate(${x}px, ${y}px)`;
arena.appendChild(el);
characters[config.name] = {
element: el,
data: config,
x, y,
hp: finalHp,
maxHp: finalHp,
atk: finalAtk,
spd: finalSpd,
moveSpeed: finalSpd,
hate: {},
lastAttack: 0,
strategy: 'idle',
target: null,
lastPos: { x, y },
stuckTimer: 0,
aiBrain: new AIBrain(config.name)
};
logMessage(`⚙️ 【${config.name}】生成: 血${finalHp} 攻${finalAtk} 速${finalSpd.toFixed(2)} (${config.hp}/${config.atk}/${config.spd})`);
updateHealth(config.name);
}
function updateHealth(name) {
const c = characters[name];
const bar = document.getElementById(`hp-ui-${name}`);
if (bar && c) {
const ratio = c.hp / c.maxHp;
bar.style.width = `${Math.max(ratio * 100, 0)}%`;
bar.style.backgroundColor = ratio > 0.6 ? '#50c878' :
ratio > 0.3 ? '#ffbb33' : '#ff4444';
}
}
function updateStrategyTag(name, strategy) {
const tag = document.getElementById(`tag-${name}`);
if (tag) {
tag.textContent = strategy;
tag.style.color = {
'attack': '#f88',
'flee': '#8f8',
'heal': '#0f0',
'speed': '#8af',
'chase': '#fa0'
}[strategy] || '#ccc';
}
}
function addExplosion(x, y) {
const explosion = document.createElement("div");
explosion.className = "explosion";
explosion.style.left = `${x - 40}px`;
explosion.style.top = `${y - 40}px`;
arena.appendChild(explosion);
setTimeout(() => explosion.remove(), 600);
}
function applyBuff(character, buff) {
if (buff.type === 'health') {
const heal = character.maxHp * 0.4;
character.hp = Math.min(character.maxHp, character.hp + heal);
logMessage(`💚 【${character.data.name}】拾取【医疗包】,恢复 ${heal.toFixed(0)} HP!`);
} else if (buff.type === 'attack') {
const boost = 6;
character.atk += boost;
logMessage(`⚡ 【${character.data.name}】拾取【攻击包】,攻击力+${boost}!`);
} else if (buff.type === 'speed') {
const boost = 0.8;
character.moveSpeed += boost;
logMessage(`🚀 【${character.data.name}】拾取【速度包】,移动速度+${boost.toFixed(1)}!`);
setTimeout(() => { character.moveSpeed = character.spd; }, 5000);
}
updateHealth(character.data.name);
buff.el.remove();
buffPoints = buffPoints.filter(b => b !== buff);
}
function attack(attackerName, defenderName) {
const now = Date.now();
const attacker = characters[attackerName];
const defender = characters[defenderName];
if (!attacker || !defender || defender.hp <= 0 || now - attacker.lastAttack < 1000) return;
attacker.lastAttack = now;
let dmg = attacker.atk;
if (attacker.data.name === "董" && Math.random() < 0.3) {
dmg *= 2;
logMessage(`🔥 【雷霆一击】${attacker.data.name} 暴击!造成 ${dmg} 伤害!!`);
} else {
logMessage(`💥 ${attacker.data.name} 攻击 ${defender.data.name},造成 ${dmg} 伤害`);
}
if (defender.data.name === "郭" && Math.random() < 0.2) {
dmg *= 0.5;
logMessage(`🛡️ 【钢铁意志】${defender.data.name} 减伤成功!`);
}
defender.hp -= dmg;
playSound(defender.hp <= 0 ? 'explode' : 'hit');
updateHealth(defenderName);
defender.element.style.transform += " scale(1.1)";
setTimeout(() => {
defender.element.style.transform = `translate(${defender.x}px, ${defender.y}px)`;
}, 150);
if (defender.hp <= 0) {
addExplosion(defender.x + 28, defender.y + 28);
logMessage(`💀 ${defender.data.name} 被击败了!`);
}
}
// ======== 🧠 AI大脑模块 ==========
class AIBrain {
constructor(name) {
this.name = name;
}
decide(self, allChars, buffs) {
const alive = allChars.filter(c => c.hp > 0 && c.data.name !== self.data.name);
if (alive.length === 0) return { action: 'idle', target: null };
const lowHp = self.hp < self.maxHp * 0.4;
const midHp = self.hp < self.maxHp * 0.7;
const nearestBuff = findNearestBuff(self);
// 逃跑逻辑
if (lowHp && nearestBuff?.type === 'health' && distance(self, nearestBuff) < 200) {
return { action: 'heal', target: nearestBuff };
}
if (lowHp) {
return { action: 'flee', target: null };
}
// 抢包逻辑
if (midHp && nearestBuff?.type === 'health') {
return { action: 'heal', target: nearestBuff };
}
if (self.atk < 30 && nearestBuff?.type === 'attack') {
return { action: 'attack', target: nearestBuff };
}
if (nearestBuff?.type === 'speed') {
return { action: 'speed', target: nearestBuff };
}
// 战斗逻辑
const target = alive.reduce((a, b) => distance(self, a) < distance(self, b) ? a : b);
return { action: 'chase', target };
}
}
function startGame() {
const totalPoints = chars.map(c => c.hp + c.atk + c.spd);
if (totalPoints.some(p => p !== 100) && !confirm("有人技能点不等于100,是否继续?")) return;
resetArena();
chars.forEach(createCharacter);
gameRunning = true;
log.innerHTML = "【系统】🎮 智能战场启动!\n";
['health', 'attack', 'speed'].forEach(t => spawnBuffPoint(t));
setInterval(() => {
if (!gameRunning) return;
const types = ['health', 'attack', 'speed'];
spawnBuffPoint(types[Math.floor(Math.random() * 3)]);
}, 8000);
function getAlive() {
return Object.values(characters).filter(c => c.hp > 0);
}
function gameLoop() {
if (!gameRunning) return;
const alive = getAlive();
if (alive.length <= 1) {
const winner = alive[0]?.data.name;
if (winner) winCount[winner]++;
statsEl.innerHTML = `🏆 胜率统计:<br>` +
`董: ${winCount['董']}胜|` +
`邱: ${winCount['邱']}胜|` +
`郭: ${winCount['郭']}胜|` +
`陆: ${winCount['陆']}胜`;
endGame(alive.length ? `🏆 胜者:【${winner}】` : "🔚 全军覆没");
setTimeout(() => {
if (confirm("本轮结束!是否开启下一轮?")) {
startGame();
}
}, 500);
return;
}
for (const name in characters) {
const self = characters[name];
if (self.hp <= 0) continue;
// AI 决策
const decision = self.aiBrain.decide(self, Object.values(characters), buffPoints);
updateStrategyTag(name, decision.action);
let moveX = 0, moveY = 0;
const baseSpeed = self.moveSpeed * 1.2;
if (decision.action === 'heal' || decision.action === 'attack' || decision.action === 'speed') {
const goal = decision.target;
const dx = goal.x - self.x;
const dy = goal.y - self.y;
const dist = distance(self, goal);
if (dist > 40) {
moveX = (dx / dist) * baseSpeed;
moveY = (dy / dist) * baseSpeed;
} else {
if (goal.el) applyBuff(self, goal);
}
} else if (decision.action === 'flee') {
const enemies = alive.filter(c => c.data.name !== self.data.name);
const threat = enemies.reduce((a, b) => distance(self, a) < distance(self, b) ? a : b);
const dx = self.x - threat.x;
const dy = self.y - threat.y;
const dist = distance(self, threat);
moveX = (dx / dist) * baseSpeed;
moveY = (dy / dist) * baseSpeed;
} else if (decision.action === 'chase' && decision.target) {
const target = decision.target;
const dx = target.x - self.x;
const dy = target.y - self.y;
const dist = distance(self, target);
if (dist > 50) {
moveX = (dx / dist) * baseSpeed;
} else if (dist > 30) {
// 走A:保持距离
moveX = (dx / dist) * (baseSpeed * 0.5);
moveY = (dy / dist) * (baseSpeed * 0.5);
if (Date.now() - self.lastAttack > 1000) attack(name, target.data.name);
} else {
// 小幅后退绕行
const angle = Math.atan2(dy, dx);
moveX = Math.cos(angle + Math.PI/2) * baseSpeed;
moveY = Math.sin(angle + Math.PI/2) * baseSpeed;
if (Date.now() - self.lastAttack > 1000) attack(name, target.data.name);
}
}
// 排斥力
for (const other of alive) {
if (other.data.name === name) continue;
const d = distance(self, other);
if (d < 60) {
const push = (60 - d) * 0.6;
const dirX = (self.x - other.x) / d;
const dirY = (self.y - other.y) / d;
moveX += dirX * push;
moveY += dirY * push;
}
}
self.x += moveX;
self.y += moveY;
self.x = Math.max(28, Math.min(arena.clientWidth - 28, self.x));
self.y = Math.max(28, Math.min(arena.clientHeight - 28, self.y));
self.element.style.transform = `translate(${self.x}px, ${self.y}px)`;
}
window.gameLoopId = requestAnimationFrame(gameLoop);
}
setTimeout(() => requestAnimationFrame(gameLoop), 1000);
}
function endGame(msg) {
logMessage(msg);
gameRunning = false;
}
function playSound(type) {
try {
const ctx = new (window.AudioContext || window.webkitAudioContext)();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain); gain.connect(ctx.destination);
if (type === 'hit') {
osc.frequency.setValueAtTime(180, ctx.currentTime);
osc.type = 'square'; gain.gain.setValueAtTime(0.15, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3);
osc.start(); osc.stop(ctx.currentTime + 0.3);
} else if (type === 'explode') {
osc.frequency.setValueAtTime(120, ctx.currentTime);
osc.type = 'sawtooth'; gain.gain.setValueAtTime(0.3, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.6);
osc.start(); osc.stop(ctx.currentTime + 0.6);
}
} catch (e) {}
}
</script>
</body>
</html>
,这个代码有bug,开始后他们会自动站成1列
最新发布