🎮ArcadeLab

Clash Royale Arcade Edition

by LuckyFlare17
641 lines18.5 KB
▶ Play
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Clash Royale Arcade Edition</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            background-color: #141923;
            color: white;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            overflow: hidden;
        }

        #screen-container {
            position: relative;
            width: 450px;
            height: 750px;
            box-shadow: 0 15px 35px rgba(0,0,0,0.6);
            border-radius: 12px;
            overflow: hidden;
            background: #2c3e50;
        }

        /* MENÜ SCREEN */
        #menu-screen {
            position: absolute;
            width: 100%;
            height: 100%;
            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: space-between;
            padding: 40px 20px;
            box-sizing: border-box;
            z-index: 10;
            transition: opacity 0.5s ease;
        }

        h1 {
            font-size: 32px;
            text-transform: uppercase;
            text-shadow: 2px 4px 0px #000;
            letter-spacing: 2px;
            margin-top: 20px;
            color: #ffcc00;
            text-align: center;
        }

        .deck-title {
            font-size: 18px;
            color: #fff;
            margin-bottom: 10px;
            text-transform: uppercase;
        }

        #deck-container {
            display: flex;
            gap: 15px;
            justify-content: center;
            width: 100%;
        }

        .card {
            background: #2c3e50;
            border: 3px solid #bdc3c7;
            border-radius: 8px;
            padding: 15px 10px;
            width: 90px;
            text-align: center;
            box-shadow: 0 5px 15px rgba(0,0,0,0.3);
        }

        .card.active-card {
            border-color: #ffcc00;
            background: #34495e;
            transform: scale(1.05);
        }

        .card-name {
            font-weight: bold;
            font-size: 14px;
            margin-bottom: 5px;
        }

        .card-level {
            font-size: 12px;
            color: #ffcc00;
            background: rgba(0,0,0,0.4);
            padding: 2px 5px;
            border-radius: 4px;
            display: inline-block;
        }

        #start-btn {
            background: linear-gradient(to bottom, #ffcc00 0%, #ff9900 100%);
            border: none;
            border-bottom: 5px solid #cc6600;
            color: #000;
            font-size: 24px;
            font-weight: bold;
            padding: 15px 60px;
            border-radius: 30px;
            cursor: pointer;
            text-transform: uppercase;
            box-shadow: 0 5px 15px rgba(0,0,0,0.4);
            transition: transform 0.1s;
        }

        #start-btn:active {
            transform: translateY(3px);
            border-bottom-width: 2px;
        }

        /* GAME SCREEN */
        #game-screen {
            display: none;
            width: 100%;
            height: 100%;
        }

        canvas {
            display: block;
            background-color: #27ae60;
        }

        /* IN-GAME HUD DECK */
        #ingame-deck {
            position: absolute;
            bottom: 10px;
            left: 50%;
            transform: translateX(-50%);
            display: flex;
            gap: 10px;
            background: rgba(0, 0, 0, 0.6);
            padding: 8px;
            border-radius: 10px;
            z-index: 5;
        }

        .hud-card {
            width: 70px;
            height: 85px;
            background: #34495e;
            border: 2px solid #7f8c8d;
            border-radius: 6px;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            font-size: 11px;
            cursor: pointer;
            user-select: none;
        }

        .hud-card.selected {
            border-color: #ffcc00;
            background: #2c3e50;
            box-shadow: 0 0 10px #ffcc00;
        }

        .hud-cost {
            background: #9b59b6;
            color: white;
            border-radius: 50%;
            width: 18px;
            height: 18px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            margin-top: 4px;
        }
    </style>
</head>
<body>

    <div id="screen-container">
        <div id="menu-screen">
            <h1>Clash Arcade<br><span style="font-size: 18px; color: #fff;">Level Edition</span></h1>
            
            <div>
                <div class="deck-title" style="text-align: center;">Dein Kampfdeck</div>
                <div id="deck-container">
                    <div class="card">
                        <div class="card-name">Ritter</div>
                        <div class="card-level" id="lvl-knight">Lvl 1</div>
                    </div>
                    <div class="card">
                        <div class="card-name">Bogenschütze</div>
                        <div class="card-level" id="lvl-archer">Lvl 1</div>
                    </div>
                </div>
            </div>

            <button id="start-btn">Kampf!</button>
        </div>

        <div id="game-screen">
            <canvas id="gameCanvas" width="450" height="640"></canvas>
            
            <div id="ingame-deck">
                <div class="hud-card selected" id="card-Knight" onclick="selectUnit('Knight')">
                    <span>⚔️ Ritter</span>
                    <span class="hud-cost">3</span>
                </div>
                <div class="hud-card" id="card-Archer" onclick="selectUnit('Archer')">
                    <span>🏹 Schütze</span>
                    <span class="hud-cost">2</span>
                </div>
            </div>
        </div>
    </div>

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

// --- GAME CONFIG & LEVEL SYSTEM ---
const BLUE_TEAM = "#2980b9";
const RED_TEAM = "#c0392b";
const GOLD = "#f1c40f";

// Persistente Truppen-Level & XP
let levels = {
    Knight: 1,
    Archer: 1
};
let xp = {
    Knight: 0,
    Archer: 0
};
const XP_NEEDED = 3; // Wie oft man setzen muss für ein Level-Up

let blueElixir = 5.0;
let redElixir = 5.0;
let selectedCard = "Knight";
let botSpawnCooldown = 0;

let towers = [];
let units = [];
let gameActive = false;

// --- DOM ELEMENTE ---
const menuScreen = document.getElementById("menu-screen");
const gameScreen = document.getElementById("game-screen");
const startBtn = document.getElementById("start-btn");

startBtn.addEventListener("click", () => {
    menuScreen.style.display = "none";
    gameScreen.style.display = "block";
    gameActive = true;
    initGame();
});

function selectUnit(type) {
    selectedCard = type;
    document.getElementById("card-Knight").classList.remove("selected");
    document.getElementById("card-Archer").classList.remove("selected");
    document.getElementById(`card-${type}`).classList.add("selected");
}

// --- ENTITÄTEN KLASSEN ---

class Tower {
    constructor(x, y, team, isKing = false) {
        this.x = x;
        this.y = y;
        this.team = team;
        this.isKing = isKing;
        this.radius = isKing ? 24 : 18;
        this.maxHp = isKing ? 3000 : 1500;
        this.hp = this.maxHp;
        this.range = 130;
        this.damage = isKing ? 45 : 30;
        this.attackCooldown = 0;
    }

    draw() {
        if (this.hp <= 0) return;

        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.team === "blue" ? BLUE_TEAM : RED_TEAM;
        ctx.fill();
        ctx.lineWidth = 3;
        ctx.strokeStyle = "#2c3e50";
        ctx.stroke();

        // Krone für den Königsturm
        if (this.isKing) {
            ctx.fillStyle = GOLD;
            ctx.font = "14px Arial";
            ctx.textAlign = "center";
            ctx.fillText("👑", this.x, this.y + 5);
        }

        // HP Balken
        const barWidth = this.radius * 2;
        const hpPct = Math.max(0, this.hp / this.maxHp);
        ctx.fillStyle = "#7f8c8d";
        ctx.fillRect(this.x - this.radius, this.y - this.radius - 12, barWidth, 6);
        ctx.fillStyle = this.team === "blue" ? "#2ecc71" : "#e74c3c";
        ctx.fillRect(this.x - this.radius, this.y - this.radius - 12, barWidth * hpPct, 6);
    }

    update(enemies) {
        if (this.hp <= 0) return;
        if (this.attackCooldown > 0) this.attackCooldown--;

        if (this.attackCooldown === 0) {
            let target = null;
            let minDist = this.range;

            for (let enemy of enemies) {
                let dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
                if (dist < minDist) {
                    minDist = dist;
                    target = enemy;
                }
            }

            if (target) {
                target.hp -= this.damage;
                this.attackCooldown = 50;

                // Schuss-Animation
                ctx.beginPath();
                ctx.moveTo(this.x, this.y);
                ctx.lineTo(target.x, target.y);
                ctx.strokeStyle = GOLD;
                ctx.lineWidth = 2;
                ctx.stroke();
            }
        }
    }
}

class Unit {
    constructor(x, y, team, type, level) {
        this.x = x;
        this.y = y;
        this.team = team;
        this.type = type;
        this.level = level;

        // Skalierung der Stats basierend auf dem Level (+15% pro Level)
        const statMultiplier = 1 + (level - 1) * 0.15;

        if (type === "Knight") {
            this.hp = Math.round(600 * statMultiplier);
            this.damage = Math.round(35 * statMultiplier);
            this.range = 22;
            this.speed = 1.1;
            this.radius = 10;
        } else if (type === "Archer") {
            this.hp = Math.round(260 * statMultiplier);
            this.damage = Math.round(22 * statMultiplier);
            this.range = 95;
            this.speed = 1.4;
            this.radius = 8;
        }
        this.maxHp = this.hp;
        this.attackCooldown = 0;
    }

    draw() {
        if (this.hp <= 0) return;

        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.team === "blue" ? BLUE_TEAM : RED_TEAM;
        ctx.fill();
        ctx.strokeStyle = "#fff";
        ctx.lineWidth = 1.5;
        ctx.stroke();

        // Visuelles Detail für Fernkämpfer
        if (this.type === "Archer") {
            ctx.beginPath();
            ctx.arc(this.x, this.y, this.radius - 4, 0, Math.PI * 2);
            ctx.fillStyle = "#f39c12";
            ctx.fill();
        }

        // HP Balken
        const hpPct = Math.max(0, this.hp / this.maxHp);
        ctx.fillStyle = "#c0392b";
        ctx.fillRect(this.x - 10, this.y - this.radius - 8, 20, 3.5);
        ctx.fillStyle = "#2ecc71";
        ctx.fillRect(this.x - 10, this.y - this.radius - 8, 20 * hpPct, 3.5);
    }

    moveAndAttack(enemies, enemyTowers) {
        if (this.hp <= 0) return;
        if (this.attackCooldown > 0) this.attackCooldown--;

        let target = null;
        let targetDist = 999999;

        // 1. Suche feindliche Truppen
        for (let enemy of enemies) {
            let dist = Math.hypot(enemy.x - this.x, enemy.y - this.y);
            if (dist < targetDist) {
                targetDist = dist;
                target = enemy;
            }
        }

        // 2. Wenn keine Truppen, nimm Gebäude/Türme ins Visier
        if (!target) {
            for (let tower of enemyTowers) {
                if (tower.hp > 0) {
                    let dist = Math.hypot(tower.x - this.x, tower.y - this.y);
                    if (dist < targetDist) {
                        targetDist = dist;
                        target = tower;
                    }
                }
            }
        }

        if (!target) return;

        // Angreifen oder Laufen
        if (targetDist <= this.range) {
            if (this.attackCooldown === 0) {
                target.hp -= this.damage;
                this.attackCooldown = 45;
                
                // Fernkampf-Projektil anzeigen
                if (this.type === "Archer") {
                    ctx.beginPath();
                    ctx.moveTo(this.x, this.y);
                    ctx.lineTo(target.x, target.y);
                    ctx.strokeStyle = "#fff";
                    ctx.lineWidth = 1;
                    ctx.stroke();
                }
            }
        } else {
            // Wegfindung über die Brücken
            let tx = target.x;
            let ty = target.y;

            // Fluss-Logik (Fluss liegt auf Y-Achse 300)
            if (this.team === "blue" && this.y > 310 && ty < 290) {
                let bridgeX = this.x < canvas.width / 2 ? 90 : 360;
                if (Math.abs(this.y - 300) > 5) { tx = bridgeX; ty = 300; }
            } else if (this.team === "red" && this.y < 290 && ty > 310) {
                let bridgeX = this.x < canvas.width / 2 ? 90 : 360;
                if (Math.abs(this.y - 300) > 5) { tx = bridgeX; ty = 300; }
            }

            let dx = tx - this.x;
            let dy = ty - this.y;
            let dist = Math.hypot(dx, dy);
            if (dist > 0) {
                this.x += (dx / dist) * this.speed;
                this.y += (dy / dist) * this.speed;
            }
        }
    }
}

// --- INITIALISIERUNG & ENGINE ---

function initGame() {
    // Türme aufstellen (Spielfeldhöhe ist 640, da unten Platz fürs Deck ist)
    towers = [
        new Tower(canvas.width / 2, 570, "blue", true), // Königsturm Spieler
        new Tower(360, 490, "blue"),                   // Rechter Kronenturm Spieler
        new Tower(canvas.width / 2, 60, "red", true),   // Königsturm Bot
        new Tower(90, 140, "red")                      // Linker Kronenturm Bot
    ];
    units = [];
    blueElixir = 5.0;
    redElixir = 5.0;
}

function spawnUnit(x, y, team, type) {
    const cost = type === "Knight" ? 3 : 2;
    
    if (team === "blue") {
        // Spieler-Restriktion (Nur unter dem Fluss platzieren & Elixier-Check)
        if (y < 310 || blueElixir < cost) return false;
        blueElixir -= cost;
        
        // Level-Up System: Erfahrung sammeln
        xp[type]++;
        if (xp[type] >= XP_NEEDED) {
            levels[type]++;
            xp[type] = 0;
            document.getElementById(`lvl-${type.toLowerCase()}`).innerText = "Lvl " + levels[type];
            // Visuelles Feedback
            ctx.fillStyle = GOLD;
            ctx.font = "bold 20px Arial";
        }
        
        units.push(new Unit(x, y, "blue", type, levels[type]));
    } else {
        // Bot platziert (Nutzt das aktuelle Level des Spielers als Orientierung)
        if (redElixir < cost) return false;
        redElixir -= cost;
        units.push(new Unit(x, y, "red", type, Math.max(1, levels[type] - 1)));
    }
    return true;
}

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

    // Elixier-Regeneration
    if (blueElixir < 10) blueElixir += 0.015;
    if (redElixir < 10) redElixir += 0.018; // Bot lädt minimal schneller für die Herausforderung!

    let blueUnits = units.filter(u => u.team === "blue" && u.hp > 0);
    let redUnits = units.filter(u => u.team === "red" && u.hp > 0);
    let blueTowers = towers.filter(t => t.team === "blue" && t.hp > 0);
    let redTowers = towers.filter(t => t.team === "red" && t.hp > 0);

    units = [...blueUnits, ...redUnits];

    // INTELLIGENTER BOT-GEGNER (Entscheidet je nach Elixier)
    botSpawnCooldown++;
    if (botSpawnCooldown > 90) {
        if (redElixir >= 3) {
            let randomType = Math.random() > 0.4 ? "Knight" : "Archer";
            // Bot wählt die Lane, wo weniger eigene Türme bedroht sind, oder zufällig
            let spawnX = Math.random() > 0.5 ? 90 : 360;
            spawnUnit(spawnX, 100, "red", randomType);
            botSpawnCooldown = 0;
        }
    }

    // Sieg / Niederlage abfragen
    if (towers[0].hp <= 0) {
        gameActive = false;
        alert("Der Bot hat gewonnen! Versuche es noch einmal.");
        backToMenu();
    } else if (towers[2].hp <= 0) {
        gameActive = false;
        alert("Sieg! Du hast den Bot bezwungen!");
        backToMenu();
    }

    // Updates berechnen
    for (let tower of towers) {
        tower.update(tower.team === "blue" ? redUnits : blueUnits);
    }
    for (let unit of units) {
        if (unit.team === "blue") {
            unit.moveAndAttack(redUnits, redTowers);
        } else {
            unit.moveAndAttack(blueUnits, blueTowers);
        }
    }
}

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

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Arena-Untergrund
    ctx.fillStyle = "#27ae60";
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Der Fluss
    ctx.fillStyle = "#2980b9";
    ctx.fillRect(0, 290, canvas.width, 20);

    // Die Brücken
    ctx.fillStyle = "#d35400";
    ctx.fillRect(70, 285, 40, 30);
    ctx.fillRect(340, 285, 40, 30);

    // Brücken-Pfeiler/Linien im Fluss
    ctx.fillStyle = "#e67e22";
    ctx.fillRect(70, 290, 40, 2);
    ctx.fillRect(340, 290, 40, 2);

    // Rendere Türme und Einheiten
    for (let tower of towers) tower.draw();
    for (let unit of units) unit.draw();

    // INGAME HUD (Elixierleiste direkt auf dem Canvas gezeichnet)
    ctx.fillStyle = "rgba(0,0,0,0.4)";
    ctx.fillRect(15, 610, 180, 12);
    ctx.fillStyle = "#9b59b6";
    ctx.fillRect(15, 610, blueElixir * 18, 12);
    
    ctx.fillStyle = "#fff";
    ctx.font = "bold 11px Arial";
    ctx.textAlign = "left";
    ctx.fillText("ELIXIER: " + Math.floor(blueElixir), 20, 620);
}

function backToMenu() {
    gameScreen.style.display = "none";
    menuScreen.style.display = "flex";
}

function gameLoop() {
    update();
    draw();
    requestAnimationFrame(gameLoop);
}

// --- INPUTS ---

// Key-Bindings für schnelles Wechseln über die Tastatur
window.addEventListener("keydown", (e) => {
    if (e.key === "1") selectUnit("Knight");
    if (e.key === "2") selectUnit("Archer");
});

canvas.addEventListener("mousedown", (e) => {
    if (!gameActive) return;
    const rect = canvas.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const my = e.clientY - rect.top;
    
    spawnUnit(mx, my, "blue", selectedCard);
});

// Loop starten
gameLoop();

</script>
</body>
</html>

Game Source: Clash Royale Arcade Edition

Creator: LuckyFlare17

Libraries: none

Complexity: complex (641 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: mini-clash-royale-js-prototype-luckyflare17" to link back to the original. Then publish at arcadelab.ai/publish.