🎮ArcadeLab

Pixel Samurai vs Monster Boss

by SparkBuilder22
337 lines12.6 KB
▶ Play
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <title>Pixel Samurai vs Monster Boss</title>
    <style>
        body { margin: 0; background: #333; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; font-family: 'Courier New', Courier, monospace; }
        canvas { background: #fff; border: 5px solid #000; box-shadow: 0 0 20px rgba(0,0,0,0.5); cursor: crosshair; }
        #ui { position: absolute; width: 1000px; height: 700px; pointer-events: none; }
        .menu { position: absolute; background: rgba(255,255,255,0.95); border: 4px solid #000; padding: 40px; text-align: center; pointer-events: auto; z-index: 100; }
        button { padding: 15px 40px; font-size: 24px; background: #000; color: #fff; border: none; cursor: pointer; font-family: inherit; }
        button:hover { background: #444; }
        .hud { position: absolute; top: 10px; width: 100%; display: flex; justify-content: space-between; padding: 0 20px; box-sizing: border-box; }
        .bar { height: 25px; border: 2px solid #000; background: #eee; position: relative; }
        .hp-fill { height: 100%; transition: width 0.1s; }
        .skill-slots { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 10px; }
        .slot { width: 60px; height: 60px; background: rgba(0,0,0,0.8); border: 2px solid #fff; color: #fff; position: relative; display: flex; justify-content: center; align-items: center; font-weight: bold; }
        .cd { position: absolute; bottom: 0; width: 100%; background: rgba(255,0,0,0.5); height: 0%; }
        .hidden { display: none; }
        #g-msg { position: absolute; bottom: 100px; width: 100%; text-align: center; color: purple; font-weight: bold; font-size: 20px; }
    </style>
</head>
<body>

    <div id="start-menu" class="menu">
        <h1>PIXEL SHURA</h1>
        <p>Di chuyển: WASD | Đánh: Click Chuột</p>
        <p>Skill: 1, 2, 3, 4 | Hồi máu: Q | Thức tỉnh: G</p>
        <button onclick="init()">PLAY</button>
    </div>

    <div id="end-menu" class="menu hidden">
        <h1 id="status-text">BẠN ĐÃ CHẾT</h1>
        <button onclick="location.reload()">REPLAY</button>
    </div>

    <div id="ui" class="hidden">
        <div class="hud">
            <div>
                <div style="font-weight:bold">PLAYER HP</div>
                <div class="bar" style="width: 300px;"><div id="p-hp" class="hp-fill" style="background: #2ecc71; width: 100%;"></div></div>
            </div>
            <div id="g-msg" class="hidden">NHẤN 'G' ĐỂ THỨC TỈNH!</div>
            <div style="text-align: right;">
                <div style="font-weight:bold">BOSS HP</div>
                <div class="bar" style="width: 500px;"><div id="b-hp" class="hp-fill" style="background: #e74c3c; width: 100%;"></div></div>
            </div>
        </div>
        <div class="skill-slots">
            <div class="slot">1<div id="cd1" class="cd"></div></div>
            <div class="slot">2<div id="cd2" class="cd"></div></div>
            <div class="slot">3<div id="cd3" class="cd"></div></div>
            <div class="slot">4<div id="cd4" class="cd"></div></div>
            <div class="slot" style="border-color: #9b59b6;">G</div>
            <div class="slot" style="border-color: #2ecc71;">Q<span id="q-val" style="font-size: 10px; position:absolute; top:2px; right:2px;">5</span></div>
        </div>
    </div>

    <canvas id="game" width="1000" height="700"></canvas>

<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

let gameActive = false;
let isGMode = false;
let timeStopped = false;
let timeStopTimer = 0;
let totalDamageDealt = 0;
let shake = 0;

const p = {
    x: 200, y: 500, w: 40, h: 70, hp: 10000, maxHp: 10000, speed: 5,
    facing: 1, state: 'idle', stateTimer: 0, invul: false, qCount: 5,
    cd: { s1: 0, s2: 0, s3: 0, s4: 0, q: 0, basic: 0 }
};

const boss = {
    x: 700, y: 200, w: 180, h: 250, hp: 100000, maxHp: 100000,
    cd: { basic: 100, s1: 300, s2: 400, s3: 600 }, state: 'idle',
    wingsWidth: 120, wingsHeight: 200
};

let effects = [];
const keys = {};

window.onkeydown = (e) => keys[e.key.toLowerCase()] = true;
window.onkeyup = (e) => keys[e.key.toLowerCase()] = false;
canvas.onmousedown = () => { if(p.cd.basic <= 0 && p.state === 'idle') useBasic(); };

function init() {
    document.getElementById('start-menu').className = 'hidden';
    document.getElementById('ui').className = '';
    gameActive = true;
    requestAnimationFrame(loop);
}

function useBasic() {
    p.state = 'attack';
    p.stateTimer = 10;
    p.cd.basic = 15;
    let dmg = isGMode ? 180 : 150;
    spawnEffect('slash', p.x + (60 * p.facing), p.y + 20, 100, 10, 'red', 10);
    dealDmg(dmg, 120);
}

function dealDmg(amt, range) {
    let dist = Math.abs((p.x + p.w/2) - (boss.x + boss.w/2));
    if(dist < range) {
        let finalDmg = isGMode ? amt * 1.2 : amt;
        boss.hp -= finalDmg;
        if(!isGMode) totalDamageDealt += finalDmg;
    }
}

function spawnEffect(type, x, y, w, h, color, life, special = null) {
    effects.push({type, x, y, w, h, color, life, maxLife: life, special});
}

function update() {
    if(!gameActive) return;

    if(!timeStopped) {
        // WASD Movement
        if(p.state === 'idle' || p.state === 'attack') {
            if(keys['a']) { p.x -= p.speed; p.facing = -1; }
            if(keys['d']) { p.x += p.speed; p.facing = 1; }
            if(keys['w']) { p.y -= p.speed; }
            if(keys['s']) { p.y += p.speed; }
        }

        // Boss AI
        boss.cd.basic--;
        if(boss.cd.basic <= 0) {
            for(let i=0; i<6; i++) {
                spawnEffect('slash', p.x + (Math.random()-0.5)*100, p.y + (Math.random()-0.5)*100, 80, 5, 'yellow', 15);
            }
            if(!p.invul) p.hp -= 100;
            boss.cd.basic = 180;
        }

        // Cooldowns
        for(let k in p.cd) if(p.cd[k] > 0) p.cd[k]--;
        if(p.stateTimer > 0) p.stateTimer--;
        else if(p.state !== 'invisible') p.state = 'idle';

    } else {
        timeStopTimer--;
        if(timeStopTimer <= 0) timeStopped = false;
    }

    // Input Skills
    if(keys['1']) useSkill1();
    if(keys['2']) useSkill2();
    if(keys['3']) useSkill3();
    if(keys['4']) useSkill4();
    if(keys['g']) useSkillG();
    if(keys['q'] && p.qCount > 0 && p.cd.q <= 0) {
        p.hp = Math.min(p.maxHp, p.hp + 3000);
        p.qCount--;
        p.cd.q = 60;
        document.getElementById('q-val').innerText = p.qCount;
    }

    // UI & End Game
    document.getElementById('p-hp').style.width = (p.hp/p.maxHp*100) + '%';
    document.getElementById('b-hp').style.width = (boss.hp/boss.maxHp*100) + '%';
    document.getElementById('cd1').style.height = (p.cd.s1/180*100) + '%';
    document.getElementById('cd2').style.height = (p.cd.s2/300*100) + '%';
    document.getElementById('cd3').style.height = (p.cd.s3/420*100) + '%';
    document.getElementById('cd4').style.height = (p.cd.s4/900*100) + '%';
    
    if(totalDamageDealt >= 20000 && !isGMode) document.getElementById('g-msg').className = '';
    else document.getElementById('g-msg').className = 'hidden';

    if(p.hp <= 0 || boss.hp <= 0) {
        gameActive = false;
        document.getElementById('end-menu').className = 'menu';
        document.getElementById('status-text').innerText = boss.hp <= 0 ? "BẠN CHIẾN THẮNG!" : "BẠN ĐÃ CHẾT";
    }
}

// --- SKILL LOGIC ---
function useSkill1() {
    if(p.cd.s1 > 0 || p.state !== 'idle') return;
    p.cd.s1 = 180;
    if(!isGMode) {
        let i = 0;
        let itv = setInterval(() => {
            spawnEffect('slash', p.x + (100*p.facing), p.y + 20, 150, 8, 'cyan', 10);
            dealDmg(200, 250);
            if(++i >= 5) clearInterval(itv);
        }, 150);
    } else {
        p.state = 'casting'; p.stateTimer = 40;
        setTimeout(() => {
            spawnEffect('circle', p.x, p.y, 300, 300, 'purple', 30);
            dealDmg(3000, 400);
        }, 500);
    }
}

function useSkill2() {
    if(p.cd.s2 > 0 || p.state !== 'idle') return;
    p.cd.s2 = 300;
    if(!isGMode) {
        spawnEffect('slash', p.x + (200*p.facing), p.y + 30, p.w*4, 40, '#fff', 15);
        dealDmg(2000, 400);
    } else {
        timeStopped = true; timeStopTimer = 180;
        let i = 0;
        let itv = setInterval(() => {
            spawnEffect('slash', boss.x + Math.random()*200, boss.y + Math.random()*200, 100, 2, 'purple', 10);
            boss.hp -= 20;
            if(++i >= 200) clearInterval(itv);
        }, 10);
    }
}

function useSkill3() {
    if(p.cd.s3 > 0 || p.state !== 'idle') return;
    p.cd.s3 = 420;
    if(!isGMode) {
        timeStopped = true; timeStopTimer = 180;
        spawnEffect('slash', p.x + (150*p.facing), p.y + 20, 500, 30, 'gold', 20);
        dealDmg(1500, 600);
    } else {
        spawnEffect('domain', p.x, p.y, 600, 600, 'rgba(0,0,0,0.4)', 120);
        setTimeout(() => {
            for(let i=0; i<1000; i++) boss.hp -= 1;
            boss.hp -= 1000;
        }, 1000);
    }
}

function useSkill4() {
    if(p.cd.s4 > 0 || p.state !== 'idle') return;
    p.cd.s4 = 900;
    p.invul = true;
    p.state = 'casting'; p.stateTimer = 120;
    if(!isGMode) {
        setTimeout(() => {
            p.state = 'invisible';
            let i = 0;
            let itv = setInterval(() => {
                spawnEffect('slash', boss.x + (Math.random()-0.5)*300, boss.y + (Math.random()-0.5)*300, 200, 5, 'red', 10);
                boss.hp -= 70;
                if(++i >= 50) {
                    clearInterval(itv);
                    spawnEffect('circle', boss.x + boss.w/2, boss.y + boss.h/2, 400, 400, 'rgba(255,0,0,0.3)', 20);
                    boss.hp -= 500;
                    p.state = 'idle'; p.invul = false;
                }
            }, 80);
        }, 2000);
    } else {
        spawnEffect('susanoo', p.x, p.y, 800, 800, 'rgba(0,191,255,0.5)', 60);
        dealDmg(4000, 1000);
        setTimeout(() => {
            spawnEffect('circle', boss.x, 0, 400, 400, '#666', 60, 'meteor');
            boss.hp -= 6000;
            p.invul = false;
        }, 800);
    }
}

function useSkillG() {
    if(totalDamageDealt < 20000 || isGMode) return;
    p.state = 'casting'; p.stateTimer = 120; p.invul = true;
    setTimeout(() => {
        isGMode = true; p.invul = false;
    }, 2000);
}

function draw() {
    ctx.clearRect(0,0,canvas.width, canvas.height);

    // Draw Boss
    ctx.save();
    ctx.fillStyle = "#444";
    ctx.fillRect(boss.x - 100, boss.y + 50, 100, 150); // Cánh
    ctx.fillRect(boss.x + boss.w, boss.y + 50, 100, 150); 
    ctx.fillStyle = "#222";
    ctx.fillRect(boss.x, boss.y, boss.w, boss.h); // Thân
    ctx.fillRect(boss.x + 20, boss.y - 40, 50, 50); // Đầu 1
    ctx.fillRect(boss.x + 110, boss.y - 40, 50, 50); // Đầu 2
    ctx.fillStyle = "#000";
    for(let i=0; i<6; i++) ctx.fillRect(boss.x - 30, boss.y + 40 + (i*35), 40, 10); // 6 Tay
    ctx.restore();

    // Draw Player
    if(p.state !== 'invisible') {
        ctx.save();
        if(isGMode) {
            ctx.shadowBlur = 20; ctx.shadowColor = "purple";
            ctx.strokeStyle = "purple"; ctx.strokeRect(p.x-5, p.y-5, p.w+10, p.h+10);
        }
        ctx.fillStyle = "#2ecc71"; // Áo xanh (Zoro)
        ctx.fillRect(p.x, p.y, p.w, p.h);
        ctx.fillStyle = "#f1c40f"; // Tóc vàng/da
        ctx.fillRect(p.x, p.y - 15, p.w, 15);
        // Kiếm (Hình 2)
        ctx.fillStyle = "#8e44ad"; // Vỏ tím
        ctx.fillRect(p.x - 12, p.y + 30, 15, 40);
        ctx.restore();
    }

    // Draw Effects
    effects.forEach((eff, i) => {
        ctx.save();
        ctx.globalAlpha = eff.life / eff.maxLife;
        ctx.fillStyle = eff.color;
        if(eff.type === 'slash') ctx.fillRect(eff.x - eff.w/2, eff.y, eff.w, eff.h);
        if(eff.type === 'circle') {
            ctx.beginPath(); ctx.arc(eff.x, eff.y, eff.w/2, 0, Math.PI*2); ctx.fill();
        }
        if(eff.type === 'domain') {
            ctx.strokeStyle = "black"; ctx.lineWidth = 5;
            ctx.beginPath(); ctx.arc(eff.x, eff.y, eff.w/2, 0, Math.PI*2); ctx.stroke();
        }
        if(eff.type === 'susanoo') {
            ctx.fillRect(eff.x - eff.w/4, eff.y - eff.h/2, eff.w/2, eff.h);
        }
        ctx.restore();
        eff.life--;
        if(eff.life <= 0) effects.splice(i, 1);
    });
}

function loop() {
    update();
    draw();
    if(gameActive) requestAnimationFrame(loop);
}
</script>
</body>
</html>

Game Source: Pixel Samurai vs Monster Boss

Creator: SparkBuilder22

Libraries: none

Complexity: complex (337 lines, 12.6 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: pixel-samurai-vs-monster-boss-sparkbuilder22-moq0856u" to link back to the original. Then publish at arcadelab.ai/publish.