独战乾坤|二次元修仙放置
by LuckyWizard81428 lines19.3 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>独战乾坤|二次元修仙放置</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#060606;color:#e4e4e4;font-family:system-ui;-webkit-tap-highlight-color:transparent;padding:16px}
.container{max-width:720px;margin:0 auto}
.card{background:#121212;border-radius:16px;padding:20px;margin-bottom:16px}
.title-gold{color:#d4af37;font-weight:bold;font-size:22px;text-align:center;margin-bottom:12px}
.flex-row{display:flex;justify-content:space-between;padding:6px 0}
.btn{background:#771818;text-align:center;padding:14px;border-radius:12px;margin:10px 0;color:#fff}
.btn:active{opacity:0.8}
.text-red{color:#ff6666}
.text-green{color:#69ff88}
.text-blue{color:#58a0ff}
.text-purple{color:#c078ff}
img{display:block;margin:0 auto;object-fit:contain}
.char-img{width:160px;border-radius:12px}
.q-img{width:100px}
.icon{width:64px;height:64px}
.effect-wrap{position:relative}
.effect{position:absolute;width:180px;height:180px;left:50%;top:50%;transform:translate(-50%,-50%);pointer-events:none;display:none}
.effect.show{display:block;animation:flash 0.35s ease-out forwards}
@keyframes flash{0%{opacity:1;transform:translate(-50%,-50%) scale(1)}100%{opacity:0;transform:translate(-50%,-50%) scale(1.3)}}
.log-box{height:140px;overflow-y:auto;background:#0a0a0a;padding:10px;border-radius:8px;margin-top:10px;color:#ff9999;font-size:14px}
input{width:100%;background:#222;color:#fff;border:none;padding:12px;border-radius:8px;margin:8px 0;font-size:16px}
.tab{display:flex;gap:6px;margin-bottom:16px}
.tab>div{flex:1;text-align:center;padding:10px;background:#222;border-radius:8px}
.tab>div.active{background:#771818;color:#d4af37}
.page{display:none}
.page.active{display:block}
</style>
</head>
<body>
<div class="container">
<div class="tab">
<div data-page="fight" class="active">闯关</div>
<div data-page="bag">背包</div>
<div data-page="skill">功法</div>
<div data-page="pill">炼丹</div>
<div data-page="boss">试炼</div>
</div>
<!-- 闯关页 -->
<div class="page active" id="fight">
<div class="card">
<div class="title-gold">独战乾坤</div>
<img class="char-img" id="img-char">
<div class="flex-row"><span>境界</span><span id="realmName">炼气一层</span></div>
<div class="flex-row"><span>战力</span><span class="text-red" id="power">10</span></div>
<div class="flex-row"><span>修为</span><span id="expText">0/100</span></div>
<div class="flex-row"><span>灵石</span><span id="gold">50</span></div>
<div class="flex-row"><span>仙玉</span><span class="text-blue" id="jade">0</span></div>
<div class="flex-row"><span>灵根</span><span id="rootName" style="color:#ccc">凡根</span></div>
<div class="flex-row"><span>当前关卡</span><span id="curStage">1</span></div>
<div class="flex-row"><span>转生次数</span><span class="text-purple" id="rebirth">0</span></div>
</div>
<div class="card effect-wrap" id="fightCard">
<div class="title-gold">⚔战斗中</div>
<div style="display:flex;justify-content:space-around;align-items:center">
<img class="q-img" id="img-qchar">
<span style="font-size:20px">VS</span>
<div>
<div id="monsterName">山野小妖</div>
<div>血量:<span id="monsterHp">150</span>/<span id="monsterMaxHp">150</span></div>
</div>
</div>
<img class="effect" id="atkEff">
<img class="effect" id="critEff">
<img class="effect" id="killEff">
<div class="log-box" id="fightLog"></div>
</div>
<div id="fightBtns">
<div class="btn" onclick="startFight()">挑战第<span id="stageBtnNum">1</span>关</div>
<div class="btn" onclick="breakRealm()">突破境界</div>
<div class="btn" onclick="offlineGain()">领取离线收益</div>
<div class="btn" onclick="randomEvent()">触发奇遇</div>
</div>
<div class="card">
<div class="title-gold">礼包兑换</div>
<input id="codeInput" placeholder="输入兑换码">
<div class="btn" onclick="exchangeCode()">兑换</div>
</div>
<div class="btn" onclick="saveData()">存档</div>
<div class="btn" onclick="resetSave()">重置存档</div>
</div>
<!-- 背包页 -->
<div class="page" id="bag">
<div class="card"><div class="title-gold">背包-装备</div></div>
<div id="bagList"></div>
<div style="text-align:center;color:#888;padding:20px" id="bagTip">暂无装备,闯关掉落</div>
</div>
<!-- 功法页 -->
<div class="page" id="skill">
<div class="card"><div class="title-gold">功法修炼</div></div>
<div id="skillList"></div>
</div>
<!-- 炼丹页 -->
<div class="page" id="pill">
<div class="card"><div class="title-gold">炼丹房</div></div>
<div class="btn" onclick="makePill()">炼制固元丹(消耗灵石200)</div>
<div id="pillList"></div>
<div style="text-align:center;color:#888;padding:20px" id="pillTip">没有丹药</div>
</div>
<!-- BOSS试炼 -->
<div class="page" id="boss">
<div class="card"><div class="title-gold">试炼BOSS</div></div>
<div class="card">
<div style="display:flex;align-items:center;gap:12px">
<img class="icon" id="img-armor">
<div>
<div>魔主 · 试炼</div>
<div>BOSS血量:<span id="bossHp">8000</span>/<span id="bossMaxHp">8000</span></div>
</div>
</div>
<div class="btn" onclick="alert('BOSS战斗逻辑和闯关一致,可以自行扩展奖励!')">挑战BOSS</div>
</div>
</div>
</div>
<script>
// ====================== 内置图片Base64资源 ======================
const RES = {
char: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMjgwIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjI4MCIgZmlsbD0iIzA4MDgwOCIvPjxjaXJjbGUgY3g9IjEwMCIgY3k9IjcwIiByPSI0MCIgZmlsbD0iIzIyMiIvPjxwYXRoIGQ9Ik02MCAxMzBsODAtMTBsMTAgODBIMTBMMTAwIDI1MCIgc3Ryb2tlPSIjZDRhZjM3IiBzdHJva2Utd2lkdGg9IjQiIGZpbGw9Im5vbmUiLz48L3N2Zz4=",
qchar: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAgMTYwIj48cmVjdCB3aWR0aD0iMTIwIiBoZWlnaHQ9IjE2MCIgZmlsbD0iIzA4MDgwOCIvPjxjaXJjbGUgY3g9IjYwIiBjeT0iNDAiIHI9IjI0IiBmaWxsPSIjMjIyIi8+PHJlY3QgeD0iMzAiIHk9IjY1IiB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHJ4PSIxMCIgZmlsbD0iIzM0MCIvPjxwYXRoIGQ9Ik05MCA4MGwxNi0yIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMyIvPjwvc3ZnPg==",
sword: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHBhdGggZD0iTTMyIDZsLTEwIDI0bDEwIDJsMTAtMjR6IiBmaWxsPSIjZmY0NCIvPjxwYXRoIGQ9Ik0zMiA0Mmw2IDE2bC02IDJ2LTJ6IiBmaWxsPSIjZDRhZjM3Ii8+PC9zdmc+",
book: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3QgeD0iMTQiIHk9IjEyIiB3aWR0aD0iMzYiIGhlaWdodD0iNDAiIHJ4PSI0IiBmaWxsPSIjMjIyIiBzdHJva2U9IiNmZmYwODAiIHN0cm9rZS13aWR0aD0iMiIvPjxwYXRoIGQ9Ik0yMCAyNGgyNQ0yMCAzMmgzNQ0yMCAzOGgyNQoiIHN0cm9rZT0iI2ZmZjA4MCIgc3Ryb2tlLXdpZHRoPSIxIi8+PC9zdmc+",
pill: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGNpcmNsZSBjeD0iMzIiIGN5PSIzMiIgcj0iMjAiIGZpbGw9IiM5NjQwMjAiIHN0cm9rZT0iI2ZmZGMwMDAiIHN0cm9rZS13aWR0aD0iMyIvPjwvc3ZnPg==",
armor: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHBhdGggZD0iTTMyIDhsMjQgMTZ2MjBsLTI0IDI0bC0yNC0yNFYyNHoiIGZpbGw9IiMyMjIiIHN0cm9rZT0iI2Q0YWYzNyIgc3Ryb2tlLXdpZHRoPSIzIi8+PC9zdmc+",
atk: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxODAgMTgwIj48cGF0aCBkPSJNMjAgOTBsMTQwLTMwbC00MCAzMGw0MCAzMC0xNDAtMzB6IiBmaWxsPSJyb2diYSgwLDAsMSwxLDAuOCkgIzAwY2ZmLCAwKSIvPjwvc3ZnPg==",
crit: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxODAgMTgwIj48Y2lyY2xlIGN4PSI5MCIgY3k9IjkwIiByPSI3MCIgZmlsbD0icmdiYSgyNTUsMjIwLDAuOCkiLz48L3N2Zz4=",
kill: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxODAgMTgwIj48Y2lyY2xlIGN4PSI5MCIgY3k9IjkwIiByPSI3MCIgZmlsbD0icmdiYSgyMDAsMzAsMzAsMC43KSIvPjwvc3ZnPg=="
};
// 配置
const realmConfig = [
{name:"炼气一层",expNeed:100,powerAdd:12},
{name:"炼气二层",expNeed:320,powerAdd:22},
{name:"炼气三层",expNeed:650,powerAdd:38},
{name:"筑基一层",expNeed:1300,powerAdd:70},
{name:"筑基二层",expNeed:2100,powerAdd:95},
{name:"金丹初期",expNeed:4200,powerAdd:160},
{name:"金丹中期",expNeed:7500,powerAdd:240}
]
const stageConfig = [
{name:"山野小妖",maxHp:150,atk:9,def:2,expReward:30,goldReward:18,dropRate:0.3},
{name:"丛林山精",maxHp:300,atk:16,def:4,expReward:65,goldReward:40,dropRate:0.35},
{name:"黑风妖兽",maxHp:620,atk:33,def:7,expReward:140,goldReward:90,dropRate:0.4}
]
const rootList = [
{name:"凡根",rate:0.6,multi:1,color:"#ccc"},
{name:"木灵根",rate:0.2,multi:1.2,color:"#3fcc68"},
{name:"金灵根",rate:0.12,multi:1.4,color:"#ffd042"},
{name:"先天道体",rate:0.08,multi:2,color:"#ff3b3b"}
]
const eventList = [
{title:"上古遗迹",desc:"你发现一处遗迹,获得灵石+200,修为+500",gold:200,exp:500,jade:0},
{title:"仙缘老者",desc:"偶遇仙人赐宝,仙玉+10",gold:0,exp:0,jade:10},
{title:"妖兽偷袭",desc:"遭遇妖兽偷袭,损失灵石100",gold:-100,exp:0,jade:0}
]
const codeLib = {xianlu100:{gold:500,exp:1000,jade:5},dzhk666:{gold:800,exp:1500,jade:10}}
const skillTemplate = [
{id:1,name:"基础吐纳",bonus:5,lv:1,cost:100},
{id:2,name:"玄元诀",bonus:15,lv:1,cost:300},
{id:3,name:"乾坤心经",bonus:40,lv:1,cost:800}
]
// 全局数据
let player,fight={fighting:false,monster:null,monsterHp:0,log:""},equipBag=[],skillList=[],pillBag=[]
// 标签切换
document.querySelectorAll('.tab div').forEach(t=>{
t.onclick=()=>{
document.querySelectorAll('.tab div').forEach(x=>x.classList.remove('active'))
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'))
t.classList.add('active')
document.getElementById(t.dataset.page).classList.add('active')
if(t.dataset.page==='bag')renderBag()
if(t.dataset.page==='skill')renderSkill()
if(t.dataset.page==='pill')renderPill()
}
})
// 加载存档
function loadSave(){
const s=localStorage.getItem("dzhk_save")
if(s){
player=JSON.parse(s)
equipBag=JSON.parse(localStorage.getItem("equipBag")||"[]")
skillList=JSON.parse(localStorage.getItem("skillList")||JSON.stringify(skillTemplate))
pillBag=JSON.parse(localStorage.getItem("pillBag")||"[]")
}else{
player={realm:0,realmName:"炼气一层",power:10,exp:0,expMax:100,gold:50,jade:0,curStage:1,rootName:"凡根",rootMulti:1,rebirth:0,lastOffline:Date.now(),def:5,critRate:0.05,critDmg:1.5}
randomRoot()
skillList=JSON.parse(JSON.stringify(skillTemplate))
}
renderPlayer()
renderBag()
renderSkill()
renderPill()
document.getElementById('fightCard').style.display='none'
}
function randomRoot(){
const r=Math.random(),root=rootList.reduce((a,b)=>{
r<b.rate?(b.rate=999,a=b):r-=b.rate;return a
},rootList[0])
player.rootName=root.name;player.rootMulti=root.multi
}
function renderPlayer(){
document.getElementById('realmName').innerText=player.realmName
document.getElementById('power').innerText=player.power
document.getElementById('expText').innerText=player.exp+"/"+player.expMax
document.getElementById('gold').innerText=player.gold
document.getElementById('jade').innerText=player.jade
document.getElementById('rootName').innerText=player.rootName
document.getElementById('rootName').style.color=rootList.find(x=>x.name===player.rootName).color
document.getElementById('curStage').innerText=player.curStage
document.getElementById('rebirth').innerText=player.rebirth
document.getElementById('stageBtnNum').innerText=player.curStage
}
function showEffect(id){
const el=document.getElementById(id)
el.classList.add('show')
setTimeout(()=>el.classList.remove('show'),350)
}
function startFight(){
const sIdx=player.curStage-1
if(sIdx>=stageConfig.length){alert("关卡待添加");return}
const m={...stageConfig[sIdx]}
fight={fighting:true,monster:m,monsterHp:m.maxHp,log:"战斗开启\n"}
document.getElementById('fightCard').style.display='block'
document.getElementById('fightBtns').style.display='none'
document.getElementById('monsterName').innerText=m.name
document.getElementById('monsterMaxHp').innerText=m.maxHp
document.getElementById('monsterHp').innerText=m.maxHp
document.getElementById('fightLog').innerText=fight.log
fightLoop()
}
function fightLoop(){
if(!fight.fighting)return
let atk=player.power,isCrit=Math.random()<player.critRate
if(isCrit)atk=Math.floor(atk*player.critDmg)
const realDmg=Math.max(1,atk-fight.monster.def)
showEffect(isCrit?'critEff':'atkEff')
fight.monsterHp-=realDmg
fight.log+=`你${isCrit?"【暴击】":""}造成${realDmg}伤害\n`
if(fight.monsterHp<=0){
showEffect('killEff')
const addExp=Math.floor(fight.monster.expReward*player.rootMulti)
const addGold=Math.floor(fight.monster.goldReward*player.rootMulti)
const drop=Math.random()<fight.monster.dropRate
fight.log+=`击杀${fight.monster.name}!修为+${addExp},灵石+${addGold}${drop?",掉落装备!":""}`
player.exp+=addExp;player.gold+=addGold
player.curStage+=1
fight.fighting=false
checkBreak()
if(drop)dropEquip()
document.getElementById('fightCard').style.display='none'
document.getElementById('fightBtns').style.display='block'
renderPlayer()
document.getElementById('fightLog').innerText=fight.log
return
}
const mDmg=Math.max(1,fight.monster.atk-player.def)
fight.log+=`${fight.monster.name}反击,对你造成${mDmg}伤害\n`
document.getElementById('monsterHp').innerText=fight.monsterHp
document.getElementById('fightLog').innerText=fight.log
setTimeout(fightLoop,700)
}
function checkBreak(){
while(player.exp>=player.expMax && player.realm<realmConfig.length-1){
player.exp-=player.expMax
player.realm++
const lv=realmConfig[player.realm]
player.realmName=lv.name
player.expMax=lv.expNeed
player.power+=lv.powerAdd
}
renderPlayer()
}
function breakRealm(){checkBreak()}
function offlineGain(){
const now=Date.now()
const hour=(now-player.lastOffline)/1000/3600
if(hour<0.1){alert("离线时间太短");return}
const gainExp=Math.floor(hour*player.power*2*player.rootMulti)
const gainGold=Math.floor(hour*player.power*1.2*player.rootMulti)
player.exp+=gainExp;player.gold+=gainGold;player.lastOffline=now
checkBreak()
alert(`离线${hour.toFixed(1)}h\n修为+${gainExp}\n灵石+${gainGold}`)
}
function randomEvent(){
const ev=eventList[Math.floor(Math.random()*eventList.length)]
player.exp+=ev.exp;player.gold=Math.max(0,player.gold+ev.gold);player.jade+=ev.jade
renderPlayer()
alert(ev.title+"\n"+ev.desc)
}
function exchangeCode(){
const k=document.getElementById('codeInput').value.trim()
if(!codeLib[k]){alert("兑换码无效");return}
const c=codeLib[k]
player.exp+=c.exp;player.gold+=c.gold;player.jade+=c.jade
document.getElementById('codeInput').value=""
checkBreak()
alert("兑换成功")
}
function dropEquip(){
const qlist=["white","green","blue","purple","orange","red"]
const q=qlist[Math.floor(Math.random()*qlist.length)]
const base={white:5,green:12,blue:25,purple:45,orange:80,red:150}[q]
equipBag.push({id:Date.now(),name:q+"宝器",quality:q,powerAdd:base,defAdd:Math.floor(base/4),isWear:false})
localStorage.setItem("equipBag",JSON.stringify(equipBag))
renderBag()
}
function renderBag(){
const box=document.getElementById('bagList')
const tip=document.getElementById('bagTip')
if(equipBag.length===0){box.innerHTML="";tip.style.display="block";return}
tip.style.display="none"
const qcolor={white:"#ccc",green:"#3fcc68",blue:"#4896ff",purple:"#b158ff",orange:"#ff9526",red:"#ff3b3b"}
box.innerHTML=equipBag.map(eq=>`
<div class="card" style="color:${qcolor[eq.quality]}">
<div style="display:flex;align-items:center;gap:12px">
<img class="icon" src="${RES.sword}">
<div><div>${eq.name}</div><div>战力+${eq.powerAdd} 防御+${eq.defAdd}</div></div>
</div>
${eq.isWear?'<div class="text-green">✅已穿戴</div>':`<div class="btn" onclick="wearEquip(${eq.id})">穿戴</div>`}
</div>`).join('')
}
function wearEquip(id){
equipBag.forEach(x=>x.isWear=false)
const t=equipBag.find(x=>x.id===id)
if(t)t.isWear=true
localStorage.setItem("equipBag",JSON.stringify(equipBag))
renderBag()
alert("穿戴成功")
}
function renderSkill(){
const box=document.getElementById('skillList')
box.innerHTML=skillList.map(sk=>`
<div class="card">
<div style="display:flex;align-items:center;gap:12px">
<img class="icon" src="${RES.book}">
<div><div>${sk.name} Lv.${sk.lv}</div><div>战力加成:${sk.bonus}</div></div>
</div>
<div class="btn" onclick="upSkill(${sk.id})">修炼(消耗灵石${sk.cost})</div>
</div>`).join('')
}
function upSkill(id){
const sk=skillList.find(x=>x.id===id)
if(player.gold<sk.cost){alert("灵石不足");return}
player.gold-=sk.cost
sk.lv+=1;sk.bonus+=Math.floor(sk.bonus*0.3);sk.cost=Math.floor(sk.cost*1.4)
localStorage.setItem("skillList",JSON.stringify(skillList))
renderPlayer();renderSkill()
}
function renderPill(){
const box=document.getElementById('pillList')
const tip=document.getElementById('pillTip')
if(pillBag.length===0){box.innerHTML="";tip.style.display="block";return}
tip.style.display="none"
box.innerHTML=pillBag.map(pl=>`
<div class="card">
<div style="display:flex;align-items:center;gap:12px">
<img class="icon" src="${RES.pill}">
<div>${pl.name} x${pl.num}</div>
</div>
<div class="btn" onclick="usePill(${pl.id})">服用</div>
</div>`).join('')
}
function makePill(){
if(player.gold<200){alert("灵石不足");return}
player.gold-=200
const p=pillBag.find(x=>x.id===1)
if(p)p.num+=1
else pillBag.push({id:1,name:"固元丹",num:1,expAdd:800})
localStorage.setItem("pillBag",JSON.stringify(pillBag))
renderPlayer();renderPill()
alert("炼丹成功")
}
function usePill(id){
const pl=pillBag.find(x=>x.id===id)
if(!pl||pl.num<=0)return
pl.num-=1
player.exp+=pl.expAdd
localStorage.setItem("pillBag",JSON.stringify(pillBag))
checkBreak();renderPill()
alert("服用丹药,修为提升")
}
function saveData(){
player.lastOffline=Date.now()
localStorage.setItem("dzhk_save",JSON.stringify(player))
localStorage.setItem("equipBag",JSON.stringify(equipBag))
localStorage.setItem("skillList",JSON.stringify(skillList))
localStorage.setItem("pillBag",JSON.stringify(pillBag))
alert("存档成功!存档存在浏览器本地")
}
function resetSave(){
if(!confirm("确认重置全部存档?"))return
localStorage.removeItem("dzhk_save")
localStorage.removeItem("equipBag")
localStorage.removeItem("skillList")
localStorage.removeItem("pillBag")
loadSave()
}
window.onload=function(){
document.getElementById("img-char").src=RES.char;
document.getElementById("img-qchar").src=RES.qchar;
document.getElementById("img-armor").src=RES.armor;
document.getElementById("atkEff").src=RES.atk;
document.getElementById("critEff").src=RES.crit;
document.getElementById("killEff").src=RES.kill;
loadSave();
}
</script>
</body>
</html>Game Source: 独战乾坤|二次元修仙放置
Creator: LuckyWizard81
Libraries: none
Complexity: complex (428 lines, 19.3 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-luckywizard81" to link back to the original. Then publish at arcadelab.ai/publish.