Clash Royale Arcade Edition
by LuckyFlare17641 lines18.5 KB
<!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.