🎮ArcadeLab

雙人數學大逃殺|苦力怕版 HTML5|可調數學難度 + 作弊功能

by SparkShark50
890 lines24.0 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<!-- HTML5 viewport 手機適配 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>雙人數學大逃殺|苦力怕版 HTML5|可調數學難度 + 作弊功能</title>
<style>
*{
margin:0;
padding:0;
box-sizing:border-box;
font-family:monospace;
}
body{
background:#111;
min-height:100vh;
overflow:hidden;
}
#gameCanvas{
display:block;
background:#3a3a2a;
}

/* ========== 開始畫面 ========== */
#startScreen{
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
background:
  radial-gradient(circle at 30% 20%, rgba(255,120,0,0.25), transparent 45%),
  radial-gradient(circle at 70% 80%, rgba(0,0,0,0.6), transparent 55%),
  linear-gradient(180deg,#1a1a1a,#2a2a2a);
display:flex;
justify-content:center;
align-items:center;
z-index:100;
}
.startBox{
text-align:center;
color:#fff;
padding:26px;
border:2px solid rgba(255,160,0,0.5);
background:rgba(0,0,0,0.55);
border-radius:14px;
box-shadow:0 0 40px rgba(255,140,0,0.25);
max-width:460px;
max-height:95vh;
overflow-y:auto;
}
.startBox h2{
font-size:26px;
letter-spacing:3px;
color:#ffb04d;
text-shadow:0 0 12px rgba(255,140,0,0.6);
margin-bottom:10px;
}
.startBox p{
font-size:14px;
color:#ddd;
margin-bottom:12px;
line-height:1.6;
}
.settingBlock{
margin:12px 0;
padding:10px;
border:1px solid rgba(255,160,0,0.35);
border-radius:8px;
text-align:left;
}
.settingBlock h4{
color:#ffb04d;
margin-bottom:8px;
font-size:16px;
}
.optionItem{
margin:4px;
font-size:14px;
}

#startBtn{
font-size:22px;
padding:12px 30px;
cursor:pointer;
background:linear-gradient(180deg,#ff8800,#c44400);
color:#fff;
border:2px solid #ffaa33;
border-radius:10px;
letter-spacing:2px;
z-index:101;
margin-top:8px;
}
#startBtn:active{
transform:scale(0.96);
}

/* ========== 遊戲 UI ========== */
#ui{
position:absolute;
top:10px;
left:10px;
color:#fff;
font-size:14px;
background:rgba(0,0,0,0.6);
padding:10px 12px;
z-index:5;
border-left:3px solid #ff9900;
border-radius:4px;
line-height:1.6;
}
#ui .p1{
color:#ff7777;
}
#ui .p2{
color:#77aaff;
}

#zoneWarning{
position:absolute;
top:80px;
left:50%;
transform:translateX(-50%);
color:#ff4444;
font-size:18px;
font-weight:bold;
background:rgba(0,0,0,0.6);
padding:6px 12px;
border-radius:6px;
display:none;
z-index:6;
letter-spacing:2px;
}

/* ========== 虛擬按鈕|玩家1(左邊) ========== */
#p1Control{
position:absolute;
bottom:20px;
left:20px;
z-index:10;
}
.vBtn{
width:60px;
height:60px;
font-size:24px;
margin:3px;
background:rgba(255,255,255,0.08);
color:#fff;
border:2px solid rgba(255,160,0,0.45);
border-radius:12px;
touch-action:none;
}
.vBtn:active{
background:rgba(255,140,0,0.45);
}
#p1FireBtn,#p1PotionBtn{
position:absolute;
bottom:20px;
right:20px;
width:70px;
height:70px;
font-size:22px;
background:rgba(255,60,60,0.25);
color:#fff;
border:2px solid rgba(255,90,90,0.5);
border-radius:50%;
touch-action:none;
}
#p1FireBtn:active,#p1PotionBtn:active{
background:rgba(255,90,90,0.55);
}
#p1PotionBtn{
bottom:105px;
background:rgba(80,160,255,0.25);
border-color:rgba(110,180,255,0.5);
}

/* ========== 虛擬按鈕|玩家2(右中) ========== */
#p2Control{
position:absolute;
bottom:20px;
right:130px;
z-index:10;
}
#p2FireBtn,#p2PotionBtn{
position:absolute;
bottom:20px;
left:20px;
width:70px;
height:70px;
font-size:22px;
background:rgba(60,120,255,0.25);
color:#fff;
border:2px solid rgba(90,140,255,0.5);
border-radius:50%;
touch-action:none;
}
#p2FireBtn:active,#p2PotionBtn:active{
background:rgba(90,140,255,0.55);
}
#p2PotionBtn{
bottom:105px;
background:rgba(80,220,160,0.25);
border-color:rgba(110,240,180,0.5);
}

/* ========== 數學題彈窗 ========== */
#mathPanel{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
background:linear-gradient(180deg,#1c1c1c,#2a2a2a);
color:#fff;
padding:26px;
font-size:26px;
display:none;
z-index:50;
text-align:center;
border:2px solid #ff9900;
border-radius:12px;
box-shadow:0 0 30px rgba(255,140,0,0.35);
min-width:310px;
}
#mathPanel .tip{
font-size:14px;
color:#ffb04d;
margin-bottom:10px;
letter-spacing:1px;
}
#mathInput{
font-size:24px;
width:140px;
padding:8px;
margin:12px 8px;
text-align:center;
background:#000;
color:#fff;
border:2px solid #ff9900;
border-radius:6px;
}
#submitMath{
font-size:18px;
padding:8px 16px;
cursor:pointer;
background:#ff8800;
color:#fff;
border:none;
border-radius:6px;
}

/* ========== GameOver / 排行榜 ========== */
#gameOverPanel{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
background:linear-gradient(180deg,#141414,#222);
color:#fff;
padding:26px;
display:none;
z-index:60;
text-align:center;
border:2px solid #ff4444;
border-radius:14px;
min-width:330px;
}
#gameOverPanel h3{
color:#ff6666;
margin-bottom:10px;
letter-spacing:2px;
}
#gameOverPanel input{
display:block;
margin:8px auto;
padding:8px;
width:80%;
background:#000;
color:#fff;
border:1px solid #ff9900;
border-radius:4px;
}
#gameOverPanel button{
font-size:16px;
padding:10px 16px;
cursor:pointer;
background:#ff8800;
color:#fff;
border:none;
border-radius:6px;
}
</style>
</head>
<body>

<!-- 開始畫面|所有設定 -->
<div id="startScreen">
  <div class="startBox">
    <h2>雙人數學大逃殺|苦力怕版 HTML5</h2>
    <p>👤玩家1:WASD / 左下角虛擬方向鍵<br>👤玩家2:方向鍵 / 右中虛擬方向鍵<br>擊敗苦力怕後答數學題,可按Enter提交答案!<br>💻電腦鍵盤作弊:Q回滿HP|E+500分|R清苦力怕|T加速|Y關毒圈</p>

    <div class="settingBlock">
      <h4>🎯 敵人出生距離</h4>
      <div class="optionItem">
        <input type="radio" name="enemyDist" id="distNear" value="near">
        <label for="distNear">近距離|難</label>
      </div>
      <div class="optionItem">
        <input type="radio" name="enemyDist" id="distMid" value="mid" checked>
        <label for="distMid">中距離|正常</label>
      </div>
      <div class="optionItem">
        <input type="radio" name="enemyDist" id="distFar" value="far">
        <label for="distFar">遠距離|易</label>
      </div>
    </div>

    <div class="settingBlock">
      <h4>🔫 選擇槍種(兩位玩家共用槍型)</h4>
      <div class="optionItem">
        <input type="radio" name="gunType" id="gunNormal" value="normal" checked>
        <label for="gunNormal">普通步槍|平衡</label>
      </div>
      <div class="optionItem">
        <input type="radio" name="gunType" id="gunSniper" value="sniper">
        <label for="gunSniper">狙擊槍|子彈快</label>
      </div>
      <div class="optionItem">
        <input type="radio" name="gunType" id="gunShotgun" value="shotgun">
        <label for="gunShotgun">霰彈槍|大子彈</label>
      </div>
    </div>

    <!-- ✅ 新:數學難度選擇 -->
    <div class="settingBlock">
      <h4>🧮 數學題難度</h4>
      <div class="optionItem">
        <input type="radio" name="mathDiff" id="mathEasy" value="easy" checked>
        <label for="mathEasy">簡單|1–10 加法</label>
      </div>
      <div class="optionItem">
        <input type="radio" name="mathDiff" id="mathNormal" value="normal">
        <label for="mathNormal">普通|1–20 加減(無負數)</label>
      </div>
      <div class="optionItem">
        <input type="radio" name="mathDiff" id="mathHard" value="hard">
        <label for="mathHard">困難|1–12 乘法</label>
      </div>
      <div class="optionItem">
        <input type="radio" name="mathDiff" id="mathExtreme" value="extreme">
        <label for="mathExtreme">極難|加減乘混合</label>
      </div>
    </div>

    <button id="startBtn">開始雙人比賽</button>
  </div>
</div>

<!-- ✅ HTML5 Canvas 遊戲畫布 -->
<canvas id="gameCanvas"></canvas>

<!-- 📱 玩家1虛擬方向鍵(左下角) -->
<div id="p1Control">
  <div>
    <button class="vBtn" id="p1Up">↑</button>
  </div>
  <div>
    <button class="vBtn" id="p1Left">←</button>
    <button class="vBtn" id="p1Down">↓</button>
    <button class="vBtn" id="p1Right">→</button>
  </div>
</div>
<button id="p1FireBtn">🗡️</button>
<button id="p1PotionBtn">💊</button>

<!-- 📱 玩家2虛擬方向鍵(右中) -->
<div id="p2Control">
  <div>
    <button class="vBtn" id="p2Up">↑</button>
  </div>
  <div>
    <button class="vBtn" id="p2Left">←</button>
    <button class="vBtn" id="p2Down">↓</button>
    <button class="vBtn" id="p2Right">→</button>
  </div>
</div>
<button id="p2FireBtn">🗡️</button>
<button id="p2PotionBtn">💊</button>

<!-- 遊戲 UI -->
<div id="ui">
  <div class="p1">🔴玩家1</div>
  <div>❤️HP:<span id="p1hp">100</span>|⭐分數:<span id="p1score">0</span>|📈Lv.<span id="p1lv">1</span></div>
  <br>
  <div class="p2">🔵玩家2</div>
  <div>❤️HP:<span id="p2hp">100</span>|⭐分數:<span id="p2score">0</span>|📈Lv.<span id="p2lv">1</span></div>
  <div>⏱️時間:<span id="timeLeft">90</span>s|🔵安全區:<span id="safeZone">320</span>m</div>
</div>

<div id="zoneWarning">⚠️ 縮圈外!扣緊HP!</div>

<!-- 數學題彈窗 -->
<div id="mathPanel">
  <div class="tip">✅ 擊敗苦力怕!答對就可以升級!(可按Enter提交)</div>
  <div id="mathQ"></div>
  <input type="number" id="mathInput" inputmode="numeric">
  <br>
  <button id="submitMath">提交答案</button>
</div>

<!-- GameOver + 排行榜輸入 -->
<div id="gameOverPanel">
  <h3>🏁 比賽完結</h3>
  <p>🔴玩家1分數:<span id="finalP1"></span></p>
  <p>🔵玩家2分數:<span id="finalP2"></span></p>
  <p id="winnerText"></p>
  <input type="text" id="playerName" placeholder="輸入參賽者名字">
  <input type="text" id="playerClass" placeholder="輸入班級">
  <br>
  <button id="saveRank">儲存紀錄</button>
</div>

<script>
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

let gameStart = false;
let startScreen = document.getElementById("startScreen");
let startBtn = document.getElementById("startBtn");
let gameTimer = null;
let isStarting = false; // ✅ 修復開始按鈕防重複觸發
let mathDifficulty = "easy";
let noSafeZoneShrink = false; // ✅ 外掛:關閉毒圈縮圈

const GAME_DURATION = 90;
let timeLeft = GAME_DURATION;

// 雙玩家資料
let p1 = {
  x: canvas.width*0.35,
  y: canvas.height/2,
  w:40, h:40,
  hp:100, score:0, level:1, speed:2,
  keys:{w:false,a:false,s:false,d:false}
};
let p2 = {
  x: canvas.width*0.65,
  y: canvas.height/2,
  w:40, h:40,
  hp:100, score:0, level:1, speed:2,
  keys:{up:false,left:false,down:false,right:false}
};

let enemyMinSpawnDist = 220;
let gunConfig = {bulletSpeed:6, bulletSize:6};

//安全圈
let safeZoneRadius = Math.min(canvas.width, canvas.height)*0.45;
const safeZoneCenter = {x:canvas.width/2, y:canvas.height/2};

let bullets = [];
let enemies = [];
let pausedForMath = false;
let currentPlayerForMath = null;

// ========== 虛擬按鈕事件綁定函數 ==========
function bindTouch(btn, playerKeyObj, keyName){
  btn.addEventListener("touchstart", (e)=>{ e.preventDefault(); playerKeyObj[keyName]=true; })
  btn.addEventListener("touchend", (e)=>{ e.preventDefault(); playerKeyObj[keyName]=false; })
}
// P1方向掣
bindTouch(document.getElementById("p1Up"), p1.keys, "w");
bindTouch(document.getElementById("p1Down"), p1.keys, "s");
bindTouch(document.getElementById("p1Left"), p1.keys, "a");
bindTouch(document.getElementById("p1Right"), p1.keys, "d");
// P2方向掣
bindTouch(document.getElementById("p2Up"), p2.keys, "up");
bindTouch(document.getElementById("p2Down"), p2.keys, "down");
bindTouch(document.getElementById("p2Left"), p2.keys, "left");
bindTouch(document.getElementById("p2Right"), p2.keys, "right");

// P1射擊、藥水
document.getElementById("p1FireBtn").addEventListener("touchstart",(e)=>{
  e.preventDefault();
  if(gameStart && !pausedForMath) shootBullet(p1);
})
document.getElementById("p1PotionBtn").addEventListener("touchstart",(e)=>{
  e.preventDefault();
  if(gameStart && !pausedForMath){
    p1.hp = Math.min(100, p1.hp + 30);
    updateUI();
  }
})
// P2射擊、藥水
document.getElementById("p2FireBtn").addEventListener("touchstart",(e)=>{
  e.preventDefault();
  if(gameStart && !pausedForMath) shootBullet(p2);
})
document.getElementById("p2PotionBtn").addEventListener("touchstart",(e)=>{
  e.preventDefault();
  if(gameStart && !pausedForMath){
    p2.hp = Math.min(100, p2.hp + 30);
    updateUI();
  }
})

// ========== 鍵盤偵測 + ✅ 外掛按鍵 ==========
document.addEventListener("keydown",(e)=>{
  if(!gameStart || pausedForMath) return;
  const k = e.key.toLowerCase();
  // P1 WASD
  if(k==='w') p1.keys.w=true;
  if(k==='a') p1.keys.a=true;
  if(k==='s') p1.keys.s=true;
  if(k==='d') p1.keys.d=true;
  // P2 方向鍵
  if(k==='arrowup') p2.keys.up=true;
  if(k==='arrowleft') p2.keys.left=true;
  if(k==='arrowdown') p2.keys.down=true;
  if(k==='arrowright') p2.keys.right=true;

  // ✅ 外掛指令
  if(k === 'q'){
    p1.hp = 100; p2.hp = 100;
  }
  if(k === 'e'){
    p1.score +=500; p2.score +=500;
  }
  if(k === 'r'){
    enemies = [];
  }
  if(k === 't'){
    p1.speed +=2; p2.speed +=2;
  }
  if(k === 'y'){
    noSafeZoneShrink = !noSafeZoneShrink;
  }
  updateUI();
})
document.addEventListener("keyup",(e)=>{
  const k = e.key.toLowerCase();
  if(k==='w') p1.keys.w=false;
  if(k==='a') p1.keys.a=false;
  if(k==='s') p1.keys.s=false;
  if(k==='d') p1.keys.d=false;
  if(k==='arrowup') p2.keys.up=false;
  if(k==='arrowleft') p2.keys.left=false;
  if(k==='arrowdown') p2.keys.down=false;
  if(k==='arrowright') p2.keys.right=false;
})

// 空格射擊P1
document.addEventListener("keydown",(e)=>{
  if(e.code==='Space' && gameStart && !pausedForMath){
    shootBullet(p1);
  }
})

function shootBullet(player){
  let target = getNearestEnemy();
  if(!target) return;
  const dx = target.x - player.x;
  const dy = target.y - player.y;
  const dist = Math.hypot(dx,dy);
  bullets.push({
    x:player.x, y:player.y,
    vx: (dx/dist)*gunConfig.bulletSpeed,
    vy: (dy/dist)*gunConfig.bulletSpeed,
    r: gunConfig.bulletSize,
    owner: player
  })
}

function getNearestEnemy(){
  if(enemies.length===0) return null;
  let nearest = enemies[0];
  let minD = Infinity;
  for(let e of enemies){
    let d = Math.hypot(e.x-canvas.width/2, e.y-canvas.height/2);
    if(d<minD){ minD=d; nearest=e; }
  }
  return nearest;
}

// ========== 初始化遊戲 ==========
function initGame(){
  p1.hp=100; p1.score=0; p1.level=1; p1.speed=2;
  p2.hp=100; p2.score=0; p2.level=1; p2.speed=2;
  timeLeft = GAME_DURATION;
  safeZoneRadius = Math.min(canvas.width, canvas.height)*0.45;
  bullets = []; enemies = [];
  pausedForMath = false;
  noSafeZoneShrink = false;
  updateUI();
}

// ========== 開始遊戲 ✅ 修復防重複Bug ==========
function startGame(e){
  if(isStarting || gameStart) return;
  isStarting = true;
  if(gameTimer){ clearInterval(gameTimer); gameTimer=null; }
  if(e) e.preventDefault();

  //讀敵人距離設定
  if(document.getElementById("distNear").checked) enemyMinSpawnDist = 100;
  else if(document.getElementById("distMid").checked) enemyMinSpawnDist = 220;
  else if(document.getElementById("distFar").checked) enemyMinSpawnDist = 400;

  //讀槍設定
  const gunSel = document.querySelector('input[name="gunType"]:checked').value;
  if(gunSel==="normal") gunConfig = {bulletSpeed:6, bulletSize:6};
  else if(gunSel==="sniper") gunConfig = {bulletSpeed:11, bulletSize:5};
  else if(gunSel==="shotgun") gunConfig = {bulletSpeed:4, bulletSize:10};

  //讀數學難度
  mathDifficulty = document.querySelector('input[name="mathDiff"]:checked').value;

  gameStart = true;
  startScreen.style.display = "none";
  initGame();

  gameTimer = setInterval(()=>{
    if(!gameStart){ clearInterval(gameTimer); gameTimer=null; return; }
    if(pausedForMath) return;
    timeLeft--;
    if(!noSafeZoneShrink){
      safeZoneRadius -=0.4;
      if(safeZoneRadius<80) safeZoneRadius=80;
    }
    updateUI();
    if(timeLeft<=0) showGameOver();
  },1000);
  setTimeout(()=>{ isStarting = false; },300);
}
startBtn.addEventListener("click", startGame);
startBtn.addEventListener("touchstart", startGame, {passive:false});

function updateUI(){
  document.getElementById("p1hp").innerText = Math.round(p1.hp);
  document.getElementById("p1score").innerText = p1.score;
  document.getElementById("p1lv").innerText = p1.level;
  document.getElementById("p2hp").innerText = Math.round(p2.hp);
  document.getElementById("p2score").innerText = p2.score;
  document.getElementById("p2lv").innerText = p2.level;
  document.getElementById("timeLeft").innerText = timeLeft;
  document.getElementById("safeZone").innerText = Math.round(safeZoneRadius);
}

// ========== 生成苦力怕敵人 ==========
function spawnEnemy(){
  let ex,ey,valid=false;
  while(!valid){
    const side = Math.floor(Math.random()*4);
    if(side===0){ ex=Math.random()*canvas.width; ey=-40; }
    else if(side===1){ ex=canvas.width+40; ey=Math.random()*canvas.height; }
    else if(side===2){ ex=Math.random()*canvas.width; ey=canvas.height+40; }
    else { ex=-40; ey=Math.random()*canvas.height; }
    const d1 = Math.hypot(ex-p1.x, ey-p1.y);
    const d2 = Math.hypot(ex-p2.x, ey-p2.y);
    if(d1 >= enemyMinSpawnDist && d2 >= enemyMinSpawnDist) valid=true;
  }
  enemies.push({x:ex,y:ey,w:44,h:52,speed:0.6})
}

// ========== 繪畫苦力怕像素樣子 ==========
function drawCreeper(e){
  // 苦力怕主體綠色
  ctx.fillStyle="#569C48";
  ctx.fillRect(e.x, e.y, e.w, e.h);
  // 深色斑紋
  ctx.fillStyle="#3C6E34";
  ctx.fillRect(e.x+8, e.y+8, 12,12);
  ctx.fillRect(e.x+24, e.y+8, 12,12);
  ctx.fillRect(e.x+14, e.y+26,16,12);
  ctx.fillStyle="#222";
  ctx.fillRect(e.x+10, e.y+10,8,8);
  ctx.fillRect(e.x+26, e.y+10,8,8);
}

// ========== 數學題 & 升級(可選難度) ==========
const mathPanel = document.getElementById("mathPanel");
const mathQ = document.getElementById("mathQ");
const mathInput = document.getElementById("mathInput");
const submitMath = document.getElementById("submitMath");
let mathAnswer;

function openMathQuestion(whoHit){
  pausedForMath = true;
  currentPlayerForMath = whoHit;
  let a,b,op;
  if(mathDifficulty === "easy"){
    a = Math.floor(Math.random()*10)+1;
    b = Math.floor(Math.random()*10)+1;
    op = "+";
    mathAnswer = a+b;
  }else if(mathDifficulty === "normal"){
    a = Math.floor(Math.random()*20)+1;
    b = Math.floor(Math.random()*20)+1;
    if(Math.random()>0.5){
      op="+"; mathAnswer=a+b;
    }else{
      if(a<b){let t=a;a=b;b=t;}
      op="-"; mathAnswer=a-b;
    }
  }else if(mathDifficulty === "hard"){
    a = Math.floor(Math.random()*12)+1;
    b = Math.floor(Math.random()*12)+1;
    op="×";
    mathAnswer = a*b;
  }else if(mathDifficulty === "extreme"){
    const r = Math.random();
    if(r<0.33){
      a = Math.floor(Math.random()*20)+1; b = Math.floor(Math.random()*20)+1; op="+"; mathAnswer=a+b;
    }else if(r<0.66){
      a = Math.floor(Math.random()*20)+1; b = Math.floor(Math.random()*20)+1; if(a<b){let t=a;a=b;b=t;} op="-"; mathAnswer=a-b;
    }else{
      a = Math.floor(Math.random()*12)+1; b = Math.floor(Math.random()*12)+1; op="×"; mathAnswer=a*b;
    }
  }
  mathQ.innerText = `${a} ${op} ${b} = ?`;
  mathPanel.style.display = "block";
  mathInput.value = "";
  // ✅ 自動聚焦,手機彈數字鍵盤
  mathInput.focus();
}

// ✅ Enter鍵提交答案
mathInput.addEventListener("keydown", (e)=>{
  if(e.code === "Enter") submitMath.click();
})
submitMath.onclick = ()=>{
  if(Number(mathInput.value) === mathAnswer){
    currentPlayerForMath.score +=100;
    currentPlayerForMath.level +=1;
    currentPlayerForMath.speed +=0.15;
  }
  mathPanel.style.display = "none";
  pausedForMath = false;
  currentPlayerForMath = null;
  updateUI();
}

// ========== GameOver ==========
const gameOverPanel = document.getElementById("gameOverPanel");
const saveRankBtn = document.getElementById("saveRank");
const finalP1 = document.getElementById("finalP1");
const finalP2 = document.getElementById("finalP2");
const winnerText = document.getElementById("winnerText");

function showGameOver(){
  gameStart = false;
  if(gameTimer){ clearInterval(gameTimer); gameTimer=null; }
  finalP1.innerText = p1.score;
  finalP2.innerText = p2.score;
  if(p1.score>p2.score) winnerText.innerText = "🏆 玩家1勝出!";
  else if(p2.score>p1.score) winnerText.innerText = "🏆 玩家2勝出!";
  else winnerText.innerText = "🤝 雙方平手!";
  gameOverPanel.style.display = "block";
}

saveRankBtn.onclick = ()=>{
  const name = document.getElementById("playerName").value.trim();
  const cls = document.getElementById("playerClass").value.trim();
  if(!name) return;
  let rankList = JSON.parse(localStorage.getItem("pubgMathRank")) || [];
  const weekKey = new Date().getFullYear()+"-"+Math.floor(new Date().getTime()/(7*24*60*60*1000));
  const savedWeek = localStorage.getItem("rankWeekKeyPubg");
  if(savedWeek !== weekKey){ rankList = []; localStorage.setItem("rankWeekKeyPubg", weekKey); }
  rankList.push({name, cls, p1score:p1.score, p2score:p2.score});
  rankList.sort((a,b)=>Math.max(b.p1score,b.p2score)-Math.max(a.p1score,a.p2score));
  rankList = rankList.slice(0,100);
  localStorage.setItem("pubgMathRank", JSON.stringify(rankList));
  gameOverPanel.style.display = "none";
}

// ========== 主遊戲迴圈 ==========
let spawnTimer = 0;
function gameLoop(){
  ctx.clearRect(0,0,canvas.width,canvas.height);

  //畫安全圈
  ctx.beginPath();
  ctx.arc(safeZoneCenter.x, safeZoneCenter.y, safeZoneRadius,0,Math.PI*2);
  ctx.fillStyle = "rgba(60,100,180,0.18)";
  ctx.fill();
  ctx.strokeStyle="#4488ff";
  ctx.lineWidth=3;
  ctx.stroke();

  if(gameStart && !pausedForMath){
    // P1移動
    if(p1.keys.w) p1.y -= p1.speed;
    if(p1.keys.s) p1.y += p1.speed;
    if(p1.keys.a) p1.x -= p1.speed;
    if(p1.keys.d) p1.x += p1.speed;
    // P2移動
    if(p2.keys.up) p2.y -= p2.speed;
    if(p2.keys.down) p2.y += p2.speed;
    if(p2.keys.left) p2.x -= p2.speed;
    if(p2.keys.right) p2.x += p2.speed;

    //毒圈扣血
    const warnEl = document.getElementById("zoneWarning");
    const dSafe1 = Math.hypot(p1.x-safeZoneCenter.x, p1.y-safeZoneCenter.y);
    const dSafe2 = Math.hypot(p2.x-safeZoneCenter.x, p2.y-safeZoneCenter.y);
    if(dSafe1>safeZoneRadius || dSafe2>safeZoneRadius){
      warnEl.style.display="block";
      if(!noSafeZoneShrink){
        if(dSafe1>safeZoneRadius) p1.hp -=0.08;
        if(dSafe2>safeZoneRadius) p2.hp -=0.08;
      }
    }else{
      warnEl.style.display="none";
    }

    //生成敵人
    spawnTimer++;
    if(spawnTimer>120){ spawnEnemy(); spawnTimer=0; }

    //更新子彈
    for(let i=bullets.length-1;i>=0;i--){
      const b = bullets[i];
      b.x += b.vx; b.y += b.vy;
      ctx.beginPath();
      ctx.arc(b.x,b.y,b.r,0,Math.PI*2);
      ctx.fillStyle="#ff2222";
      ctx.fill();
      if(b.x<0||b.x>canvas.width||b.y<0||b.y>canvas.height) bullets.splice(i,1);
    }

    //更新苦力怕敵人
    for(let i=enemies.length-1;i>=0;i--){
      const e = enemies[i];
      const dToP1 = Math.hypot(e.x-p1.x, e.y-p1.y);
      const dToP2 = Math.hypot(e.x-p2.x, e.y-p2.y);
      let targetPlayer = dToP1 < dToP2 ? p1 : p2;
      const dx = targetPlayer.x - e.x;
      const dy = targetPlayer.y - e.y;
      const dist = Math.hypot(dx,dy);
      e.x += (dx/dist)*e.speed;
      e.y += (dy/dist)*e.speed;

      // ✅ 繪畫苦力怕
      drawCreeper(e);

      //子彈擊中苦力怕
      for(let j=bullets.length-1;j>=0;j--){
        const b = bullets[j];
        const hitD = Math.hypot(b.x-e.x, b.y-e.y);
        if(hitD < 45){
          enemies.splice(i,1);
          bullets.splice(j,1);
          openMathQuestion(b.owner);
          break;
        }
      }

      //苦力怕撞玩家扣血
      const hitP1 = Math.hypot(p1.x-e.x, p1.y-e.y);
      const hitP2 = Math.hypot(p2.x-e.x, p2.y-e.y);
      if(hitP1<35){
        p1.hp -=20; enemies.splice(i,1);
      }else if(hitP2<35){
        p2.hp -=20; enemies.splice(i,1);
      }
    }

    //畫兩位玩家
    ctx.fillStyle="#ff6666";
    ctx.fillRect(p1.x,p1.y,p1.w,p1.h);
    ctx.fillStyle="#6688ff";
    ctx.fillRect(p2.x,p2.y,p2.w,p2.h);

    updateUI();
  }

  requestAnimationFrame(gameLoop);
}
gameLoop();

window.addEventListener("resize",()=>{
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
})
</script>
</body>
</html>

Game Source: 雙人數學大逃殺|苦力怕版 HTML5|可調數學難度 + 作弊功能

Creator: SparkShark50

Libraries: none

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