🎮ArcadeLab

方塊RPG冒險

by SparkShark50
466 lines18.5 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-HK">
<head>
<meta charset="UTF-8">
<title>方塊RPG冒險</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:system-ui, sans-serif}
body{background:#222;color:#fff;overflow:hidden;font-size:12px}
#home{width:100vw;height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:24px}
#home h1{font-size:26px}
.btn{padding:8px 14px;background:#3a3a3a;border:1px solid #888;border-radius:4px;color:#fff;cursor:pointer;font-size:12px}
.btn:hover{background:#505050}
#game{width:100vw;height:100vh;display:none;position:relative;padding:12px}
.top-left{position:absolute;top:12px;left:12px;line-height:1.5;font-size:12px}
.top-right{position:absolute;top:12px;right:12px;display:flex;flex-direction:column;gap:5px;width:130px}
.battle-area{position:absolute;top:45%;left:50%;transform:translate(-50%,-50%);text-align:center;width:90%}
.monster-name{font-size:20px;margin-bottom:6px}
.hp-row{margin:3px 0;font-size:12px}
.bar-wrap{width:260px;height:14px;background:#111;border:1px solid #777;margin:6px auto;position:relative;overflow:hidden;border-radius:2px}
.bar-cd{height:100%;background:#4488ff;width:0%;transition: width 0.1s linear;}
.bar-combo{height:100%;background:#ff8822;width:0%}
.combo-mark{position:absolute;width:2px;height:100%;background:#fff;left:50%}
.combo-pointer{position:absolute;width:10px;height:100%;background:#fff888}
.combo-text{margin:3px 0;color:#ffaa22;font-weight:bold;font-size:12px}
.pred-text{margin:4px 0;color:#aaa;font-size:12px}
.modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.75);display:none;align-items:center;justify-content:center}
.modal-box{background:#2b2b2b;padding:18px;border:1px solid #999;border-radius:6px;width:320px;max-height:80vh;overflow-y:auto;font-size:12px}
.modal-box h3{margin-bottom:10px;font-size:16px}
.item-line{padding:4px 0;border-bottom:1px solid #444;font-size:12px}
input{width:100%;padding:5px;margin:5px 0;background:#1a1a1a;border:1px solid #666;color:#fff;border-radius:3px;font-size:12px}
.inv-row{display:flex;gap:4px;flex-wrap:wrap;margin:6px 0}
.inv-slot{width:42px;height:42px;background:#181818;border:1px solid #555;display:flex;align-items:center;justify-content:center;font-size:11px;position:relative}
.offhand{width:56px;height:56px;background:#181818;border:2px solid #888;margin:6px auto;display:flex;align-items:center;justify-content:center;font-size:12px}
.quality-common{color:#eee}
.quality-good{color:#55ff55}
.quality-rare{color:#55aaff}
.quality-legend{color:#ffaa00}
.tip{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#111;padding:6px 12px;border:1px solid #888;border-radius:4px;display:none;font-size:12px}
#battleTip{margin-top:8px;color:#ff7777;font-size:12px}
</style>
</head>
<body>
<div id="home">
<h1>方塊RPG冒險</h1>
<button class="btn" onclick="startGame()">開始遊戲</button>
</div>

<div id="game">
<div class="top-left">
<div>金幣:<span id="gold">0</span></div>
<div>鐵礦:<span id="iron">0</span></div>
<div>煤炭:<span id="coal">0</span></div>
<div>寶石:<span id="gem">0</span></div>
<div>擊殺普通怪:<span id="killCount">0</span>/15</div>
<div>玩家血量:<span id="playerHp">40</span>/<span id="playerMaxHp">40</span></div>
<div>臨時血量:<span id="tempHp">0</span></div>
</div>
<div class="top-right">
<button class="btn" onclick="openMine()">礦場</button>
<button class="btn" onclick="openBag()">背包</button>
<button class="btn" onclick="openOffhand()">副手</button>
<button class="btn" id="craftBtn" style="display:none" onclick="openCraft()">工作台</button>
<button class="btn" onclick="openSaveLoad()">存檔/讀檔</button>
<input id="cheatInput" placeholder="輸入作弊碼">
<button class="btn" onclick="checkCheat()">確認作弊碼</button>
</div>

<div class="battle-area">
<div class="monster-name" id="monsterName">🧟 殭屍</div>
<div class="hp-row">怪物血量:<span id="monsterHp">60</span>/<span id="monsterMaxHp">60</span></div>
<div class="pred-text" id="predText">預計大約 8 回合擊敗呢隻怪</div>
<div class="bar-wrap">
<div class="bar-cd" id="cdBar"></div>
</div>
<div style="font-size:12px">攻擊冷卻</div>
<div class="bar-wrap">
<div class="bar-combo" id="comboBar"></div>
<div class="combo-mark"></div>
<div class="combo-pointer" id="comboPointer"></div>
</div>
<div class="combo-text">連擊:<span id="comboCount">0</span></div>
<button class="btn" onclick="attack()">攻擊</button>
<div id="battleTip"></div>
</div>
</div>

<div class="modal" id="mineModal">
<div class="modal-box">
<h3>礦場</h3>
<button class="btn" onclick="mineNormal()">普通礦點</button>
<button class="btn" onclick="mineDeep()">花300金幣前往深層礦點</button>
<button class="btn" onclick="closeModal('mineModal')">關閉</button>
</div>
</div>

<div class="modal" id="craftModal">
<div class="modal-box">
<h3>工作台</h3>
<div class="item-line">石劍:石頭×2 <button class="btn" onclick="craft('stoneSword')">合成</button></div>
<div class="item-line">鐵劍:鐵礦×2 <button class="btn" onclick="craft('ironSword')">合成</button></div>
<div class="item-line">鑽石劍:寶石×3 <button class="btn" onclick="craft('diamondSword')">合成</button></div>
<div class="item-line">盾牌:鐵礦×3 <button class="btn" onclick="craft('shield')">合成</button></div>
<div class="item-line">麵包:石頭×1 <button class="btn" onclick="craft('bread')">合成</button></div>
<button class="btn" onclick="closeModal('craftModal')">關閉</button>
</div>
</div>

<div class="modal" id="bagModal">
<div class="modal-box">
<h3>背包 (<span id="bagCount">0</span>/20)</h3>
<div class="inv-row" id="bagList"></div>
<button class="btn" onclick="closeModal('bagModal')">關閉</button>
</div>
</div>

<div class="modal" id="offhandModal">
<div class="modal-box">
<h3>副手欄</h3>
<div class="offhand" id="offhandSlot">空</div>
<div id="offhandList"></div>
<button class="btn" onclick="closeModal('offhandModal')">關閉</button>
</div>
</div>

<div class="modal" id="saveModal">
<div class="modal-box">
<h3>存檔讀檔</h3>
<div>存檔代碼:</div>
<input id="saveCodeRead" readonly>
<button class="btn" onclick="saveGame()">生成存檔</button>
<div style="margin-top:6px">讀檔:</div>
<input id="saveCodeWrite" placeholder="貼存檔代碼">
<button class="btn" onclick="loadGame()">讀檔</button>
<button class="btn" onclick="closeModal('saveModal')">關閉</button>
</div>
</div>

<div class="modal" id="unlockCraftModal">
<div class="modal-box">
<h3>提示</h3>
<p>已收集10個鐵礦,可以解鎖工作台,是否解鎖?</p>
<button class="btn" onclick="unlockCraft()">解鎖</button>
<button class="btn" onclick="closeModal('unlockCraftModal')">暫時唔解鎖</button>
</div>
</div>

<div class="tip" id="tipBox"></div>

<script>
const CHEAT_CODE = "!!!gsf801005!!!";
let game = {
    gold:0, iron:0, coal:0, gem:0, stone:0,
    playerHp:40, playerMaxHp:40, tempHp:0, baseHp:40,
    bag:[], bagMax:20, offhand:null,
    craftUnlocked:false, bossUnlocks:[false,false,false,false],
    killCount:0, bossIndex:0,
    monster:{name:"🧟 殭屍", hp:60, maxHp:60, atk:6, type:"zombie"},
    cd:false, cdTime:5000,
    combo:0, comboRun:false, comboSpeed:1200, comboFast:600, comboTimer:null,
    critRate:0.15, dodgeRate:0.12,
    tempHpTimer:null, cdTimer:null
}
const BOSSES = [
    {name:"殭屍王",hp:120,maxHp:120,atk:8},
    {name:"苦力怕霸主",hp:180,maxHp:180,atk:12},
    {name:"凋靈騎士",hp:280,maxHp:280,atk:15},
    {name:"末影守衛",hp:400,maxHp:400,atk:20}
]
const QUALITY = ["普通","優良","稀有","傳奇"];
const QUALITY_COLOR = ["quality-common","quality-good","quality-rare","quality-legend"];

function startGame(){
    document.getElementById("home").style.display="none";
    document.getElementById("game").style.display="block";
    refreshUI();
}
function showTip(text){
    let t = document.getElementById("tipBox");
    t.innerText = text; t.style.display="block";
    setTimeout(()=>t.style.display="none",1600);
}
function closeModal(id){document.getElementById(id).style.display="none"}
function openModal(id){document.getElementById(id).style.display="flex"}

function openMine(){openModal("mineModal")}
function openCraft(){openModal("craftModal")}
function openBag(){renderBag();openModal("bagModal")}
function openOffhand(){renderOffhand();openModal("offhandModal")}
function openSaveLoad(){openModal("saveModal")}

function mineNormal(){
    if(game.cd){showTip("正在前往礦點……");return}
    game.cd=true;
    document.getElementById("cdBar").style.width="100%";
    showTip("正在前往礦點……");
    game.cdTimer = setTimeout(()=>{
        let r = Math.random();
        if(r<0.3){game.iron++}
        else if(r<0.5){game.coal++}
        else {game.stone++}
        game.cd=false;
        document.getElementById("cdBar").style.width="0%";
        refreshUI();
    },5000)
}
function mineDeep(){
    if(game.gold<300){showTip("金幣唔夠!");return}
    if(game.cd){showTip("正在前往礦點……");return}
    game.gold -=300; game.cd=true;
    document.getElementById("cdBar").style.width="100%";
    showTip("正在前往礦點……");
    game.cdTimer = setTimeout(()=>{
        let r = Math.random();
        if(r<0.4){game.gem++}
        else if(r<0.7){game.iron +=2}
        else {game.coal +=2}
        game.cd=false;
        document.getElementById("cdBar").style.width="0%";
        refreshUI();
    },5000)
}

function unlockCraft(){
    game.craftUnlocked=true;
    document.getElementById("craftBtn").style.display="flex";
    closeModal("unlockCraftModal");
    showTip("工作台已解鎖!");
    refreshUI();
}
function checkIronUnlock(){
    if(game.iron>=10 && !game.craftUnlocked){
        openModal("unlockCraftModal");
    }
}

function craft(id){
    if(id=="stoneSword"){
        if(game.stone<2){showTip("材料唔夠");return}
        if(game.bag.length>=game.bagMax){showTip("背包滿咗");return}
        game.stone -=2;
        let q = Math.floor(Math.random()*4);
        game.bag.push({name:"石劍",type:"weapon",dmg:9,quality:QUALITY[q],color:QUALITY_COLOR[q]})
    }else if(id=="ironSword"){
        if(game.iron<2){showTip("材料唔夠");return}
        if(game.bag.length>=game.bagMax){showTip("背包滿咗");return}
        game.iron -=2;
        let q = Math.floor(Math.random()*4);
        game.bag.push({name:"鐵劍",type:"weapon",dmg:12,quality:QUALITY[q],color:QUALITY_COLOR[q]})
    }else if(id=="diamondSword"){
        if(game.gem<3){showTip("材料唔夠");return}
        if(game.bag.length>=game.bagMax){showTip("背包滿咗");return}
        game.gem -=3;
        let q = Math.floor(Math.random()*4);
        game.bag.push({name:"鑽石劍",type:"weapon",dmg:18,quality:QUALITY[q],color:QUALITY_COLOR[q]})
    }else if(id=="shield"){
        if(game.iron<3){showTip("材料唔夠");return}
        if(game.bag.length>=game.bagMax){showTip("背包滿咗");return}
        game.iron -=3;
        game.bag.push({name:"鐵盾牌",type:"shield",reduce:0.3})
    }else if(id=="bread"){
        if(game.stone<1){showTip("材料唔夠");return}
        if(game.bag.length>=game.bagMax){showTip("背包滿咗");return}
        game.stone -=1;
        game.bag.push({name:"麵包",type:"food",heal:15})
    }
    refreshUI(); renderBag();
}

function renderBag(){
    let wrap = document.getElementById("bagList");
    wrap.innerHTML="";
    document.getElementById("bagCount").innerText = game.bag.length;
    game.bag.forEach((it,i)=>{
        let d = document.createElement("div");
        d.className="inv-slot "+(it.color||"");
        d.innerText=it.name;
        d.onclick=()=>useItem(i);
        wrap.appendChild(d);
    })
}
function renderOffhand(){
    document.getElementById("offhandSlot").innerText = game.offhand?.name||"空";
    let list = document.getElementById("offhandList"); list.innerHTML="";
    game.bag.forEach((it,i)=>{
        if(it.type=="shield"){
            let b = document.createElement("button");
            b.className="btn"; b.innerText="裝"+it.name;
            b.onclick=()=>{game.offhand=it;game.bag.splice(i,1);renderOffhand();refreshUI()}
            list.appendChild(b);
        }
    })
}
function useItem(idx){
    let it = game.bag[idx];
    if(it.name=="麵包"){
        game.playerHp = Math.min(game.playerHp+it.heal, game.playerMaxHp);
        game.bag.splice(idx,1);
    }else if(it.name=="腐肉"){
        game.baseHp = game.playerHp;
        game.tempHp = 5;
        game.playerHp +=5;
        if(game.tempHpTimer) clearInterval(game.tempHpTimer);
        game.tempHpTimer = setInterval(()=>{
            game.tempHp -=0.5; game.playerHp -=0.5;
            if(game.tempHp<=0){game.tempHp=0;game.playerHp=game.baseHp;clearInterval(game.tempHpTimer)}
            refreshUI();
        },1000)
        game.bag.splice(idx,1);
    }
    refreshUI(); renderBag();
}

function startCombo(){
    game.comboRun=true;
    let speed = game.monster.type=="babyZombie" ? game.comboFast : game.comboSpeed;
    let ptr = document.getElementById("comboPointer");
    let pos = 0;
    clearInterval(game.comboTimer);
    game.comboTimer = setInterval(()=>{
        pos += 4;
        if(pos>100){pos=0;game.combo=0;game.comboRun=false;clearInterval(game.comboTimer)}
        ptr.style.left = pos+"%";
    }, speed/100);
}
function inComboZone(){
    let s = document.getElementById("comboPointer").style.left;
    let p = parseInt(s);
    return p>=45 && p<=55;
}
function attack(){
    if(game.cd){showTip("緊係冷卻!");return}
    game.cd=true;
    document.getElementById("cdBar").style.width="100%";
    let dmg = 8;
    if(inComboZone()){game.combo++; dmg *= 1 + game.combo*0.15; showTip("連擊!x"+game.combo)}
    else {game.combo=0}
    if(Math.random()<game.critRate){dmg *=1.5; showTip("暴擊!")}
    game.monster.hp -= dmg;
    updatePred();
    if(game.monster.hp <=0){killMonster();resetCombo()}
    else{monsterAttack();startCombo()}

    game.cdTimer = setTimeout(()=>{
        game.cd=false;
        document.getElementById("cdBar").style.width="0%";
        refreshUI();
    },game.cdTime)
    refreshUI();
}
function resetCombo(){
    game.comboRun=false; game.combo=0; clearInterval(game.comboTimer);
    document.getElementById("comboPointer").style.left="0%";
}
function monsterAttack(){
    if(Math.random()<game.dodgeRate){showTip("閃避成功!");return}
    let md = game.monster.atk;
    if(game.monster.name=="🧟 殭屍" && Math.random()<0.25){md *=1.4;showTip("殭屍重擊!")}
    if(game.monster.name=="【苦力怕】"){md *=1.8;showTip("苦力怕爆炸!")}
    if(game.offhand?.type==="shield"){
        md = md * (1-game.offhand.reduce);
    }
    game.playerHp -= md;
    if(game.playerHp<=0){game.playerHp=0;showTip("你敗咗!重置怪物");spawnNormalMonster()}
    refreshUI();
}
function killMonster(){
    resetCombo();
    let m = game.monster;
    if(m.name.includes("殭屍")){
        let meat = {name:"腐肉",type:"food"};
        for(let i=0;i<Math.floor(Math.random()*2)+1;i++){
            if(game.bag.length<game.bagMax) game.bag.push(meat);
        }
        let extra = [{name:"麵包",type:"food",heal:15},{name:"箭"},{name:"附魔簿"},{name:"煤炭"}];
        if(Math.random()<0.35 && game.bag.length<game.bagMax){
            game.bag.push(extra[Math.floor(Math.random()*extra.length)])
        }
    }
    if(m.name=="【苦力怕】"){
        if(game.bag.length<game.bagMax) game.bag.push({name:"火藥"})
    }
    if(!m.name.includes("王") && !m.name.includes("霸主") && !m.name.includes("騎士") && !m.name.includes("末影守衛")){
        game.killCount++;
        if(game.killCount>=15){
            spawnBoss();
            game.killCount=0;
        }else spawnNormalMonster();
    }else{
        game.bossIndex++;
        if(game.bossIndex>=BOSSES.length) game.bossIndex=0;
        spawnNormalMonster();
    }
    refreshUI(); checkIronUnlock();
}
function spawnNormalMonster(){
    let r = Math.random();
    if(r<0.4){game.monster={name:"🧟 殭屍",hp:60,maxHp:60,atk:6,type:"zombie"}}
    else if(r<0.7){game.monster={name:"細殭屍",hp:35,maxHp:35,atk:4,type:"babyZombie"}}
    else {game.monster={name:"【苦力怕】",hp:50,maxHp:50,atk:9,type:"creeper"}}
    updatePred();
}
function spawnBoss(){
    let b = BOSSES[game.bossIndex];
    game.monster = {...b, type:"boss"};
    showTip("Boss出現!"+b.name);
    updatePred();
}
function updatePred(){
    let avgDmg = 8 * 1.25;
    let turns = Math.ceil(game.monster.hp / avgDmg);
    document.getElementById("predText").innerText = "預計大約 "+turns+" 回合擊敗呢隻怪";
}

function checkCheat(){
    let input = document.getElementById("cheatInput").value.trim();
    if(input === CHEAT_CODE){
        // 真正全部解鎖
        game.craftUnlocked=true;
        game.bossUnlocks=[true,true,true,true];
        game.gold += 2000;
        game.iron += 50;
        game.gem += 20;
        game.stone +=30;
        game.bag.push({name:"鑽石劍",type:"weapon",dmg:18,quality:"傳奇",color:"quality-legend"});
        game.bag.push({name:"鐵盾牌",type:"shield",reduce:0.3});
        game.bag.push({name:"附魔簿"});
        document.getElementById("craftBtn").style.display="flex";
        showTip("已解鎖全部內容!高級裝備同材料已發放");
    }else showTip("作弊碼無效");
    document.getElementById("cheatInput").value="";
    refreshUI();
}

function saveGame(){
    let s = btoa(JSON.stringify(game));
    document.getElementById("saveCodeRead").value = s;
}
function loadGame(){
    let c = document.getElementById("saveCodeWrite").value.trim();
    try{
        game = JSON.parse(atob(c));
        if(game.craftUnlocked) document.getElementById("craftBtn").style.display="flex";
        showTip("讀檔成功");
        refreshUI();
    }catch(e){showTip("存檔代碼無效")}
}

function refreshUI(){
    document.getElementById("gold").innerText = game.gold;
    document.getElementById("iron").innerText = game.iron;
    document.getElementById("coal").innerText = game.coal;
    document.getElementById("gem").innerText = game.gem;
    document.getElementById("killCount").innerText = game.killCount;
    document.getElementById("playerHp").innerText = Math.max(0,game.playerHp.toFixed(1));
    document.getElementById("playerMaxHp").innerText = game.playerMaxHp;
    document.getElementById("tempHp").innerText = game.tempHp.toFixed(1);
    document.getElementById("monsterName").innerText = game.monster.name;
    document.getElementById("monsterHp").innerText = Math.max(0,game.monster.hp.toFixed(1));
    document.getElementById("monsterMaxHp").innerText = game.monster.maxHp;
    document.getElementById("comboCount").innerText = game.combo;
    updatePred();
}
refreshUI();
</script>
</body>
</html>

Game Source: 方塊RPG冒險

Creator: SparkShark50

Libraries: none

Complexity: complex (466 lines, 18.5 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: rpg-sparkshark50-mvv4gi21" to link back to the original. Then publish at arcadelab.ai/publish.