Pertarungan Abad Pertengahan
by CosmicOtter50501 lines16.6 KB
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Pertarungan Abad Pertengahan</title>
<style>
* {
box-sizing: border-box;
user-select: none;
-webkit-user-select: none;
}
body {
margin: 0;
padding: 0;
background-color: #1a1a1a;
color: #ffffff;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
min-height: 100vh;
overflow: hidden;
}
#game-container {
width: 100%;
max-width: 600px;
height: 100vh;
display: flex;
flex-direction: column;
background: #2b2b2b;
border-left: 2px solid #444;
border-right: 2px solid #444;
}
/* Panel Atas (Info) */
#header {
background: #1f1f1f;
padding: 10px;
display: flex;
justify-content: space-around;
align-items: center;
border-bottom: 2px solid #444;
font-size: 14px;
}
.stat-box {
background: #333;
padding: 5px 10px;
border-radius: 5px;
border: 1px solid #555;
}
/* Arena Pertarungan */
#battlefield {
flex: 1;
position: relative;
background: linear-gradient(to bottom, #4a773d 0%, #355e2b 100%);
overflow: hidden;
border-bottom: 2px solid #444;
}
.base {
position: absolute;
bottom: 20px;
width: 60px;
height: 120px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
font-weight: bold;
font-size: 12px;
}
#player-base { left: 15px; color: #6bc5ff; }
#enemy-base { right: 15px; color: #ff6b6b; }
.base-tower {
width: 50px;
height: 90px;
background: #555;
border: 3px solid #333;
border-radius: 5px 5px 0 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
/* Unit Karakter */
.unit {
position: absolute;
bottom: 30px;
width: 35px;
height: 45px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: bold;
border-radius: 4px;
transition: left 0.05s linear;
}
.player-unit { background: #3498db; border: 2px solid #2980b9; }
.enemy-unit { background: #e74c3c; border: 2px solid #c0392b; }
.miner { background: #f1c40f; border: 2px solid #d4ac0d; color: #000; }
/* Panel Kontrol Bawah */
#controls {
background: #1f1f1f;
padding: 10px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
border-top: 2px solid #444;
}
.btn {
background: #444;
color: white;
border: none;
padding: 10px 5px;
border-radius: 6px;
font-size: 11px;
font-weight: bold;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: 0.1s;
}
.btn:active {
transform: scale(0.95);
}
.btn:disabled {
background: #2a2a2a;
color: #666;
cursor: not-allowed;
}
.btn-miner { background: #b7950b; }
.btn-swordsman { background: #2471a3; }
.btn-knight { background: #117864; }
.btn-archer { background: #7d3c98; }
/* Layar Pesan / Game Over */
#message-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
display: none;
}
#message-overlay h1 {
margin-bottom: 20px;
font-size: 24px;
text-align: center;
}
.restart-btn {
padding: 12px 24px;
background: #27ae60;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
}
</style>
</head>
<body>
<div id="game-container">
<!-- Header Informasi -->
<div id="header">
<div class="stat-box">💰 Emas: <span id="gold-display">50</span></div>
<div class="stat-box">⛏️ Penambang: <span id="miner-count">0</span></div>
<div class="stat-box">🏰 HP Markas: <span id="base-hp">100</span></div>
</div>
<!-- Arena Pertarungan -->
<div id="battlefield">
<div id="player-base" class="base">
<div class="base-tower" id="p-hp-bar">100</div>
<span>Kita</span>
</div>
<div id="enemy-base" class="base">
<div class="base-tower" id="e-hp-bar">100</div>
<span>Musuh</span>
</div>
</div>
<!-- Panel Kontrol Tombol -->
<div id="controls">
<button class="btn btn-miner" onclick="spawnMiner()">
<span>⛏️ Penambang</span>
<span>(25 🪙)</span>
</button>
<button class="btn btn-swordsman" onclick="spawnUnit('swordsman', 20)">
<span>⚔️ Pejuang</span>
<span>(20 🪙)</span>
</button>
<button class="btn btn-archer" onclick="spawnUnit('archer', 30)">
<span>🏹 Pemanah</span>
<span>(30 🪙)</span>
</button>
<button class="btn btn-knight" onclick="spawnUnit('knight', 50)">
<span>🛡️ Ksatria</span>
<span>(50 🪙)</span>
</button>
</div>
</div>
<!-- Layar Selesai Game -->
<div id="message-overlay">
<h1 id="game-message">KEMENANGAN!</h1>
<button class="restart-btn" onclick="resetGame()">Main Lagi</button>
</div>
<script>
// --- Web Audio API (Efek Suara Tanpa File Eksternal) ---
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playSound(type) {
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
const osc = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
osc.connect(gainNode);
gainNode.connect(audioCtx.destination);
let now = audioCtx.currentTime;
if (type === 'gold') {
osc.type = 'sine';
osc.frequency.setValueAtTime(600, now);
osc.frequency.exponentialRampToValueAtTime(900, now + 0.1);
gainNode.gain.setValueAtTime(0.1, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
osc.start(now);
osc.stop(now + 0.1);
} else if (type === 'spawn') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(150, now);
osc.frequency.linearRampToValueAtTime(300, now + 0.15);
gainNode.gain.setValueAtTime(0.15, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
osc.start(now);
osc.stop(now + 0.15);
} else if (type === 'hit') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(100, now);
osc.frequency.linearRampToValueAtTime(40, now + 0.1);
gainNode.gain.setValueAtTime(0.2, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
osc.start(now);
osc.stop(now + 0.1);
} else if (type === 'win') {
osc.type = 'sine';
osc.frequency.setValueAtTime(300, now);
osc.frequency.setValueAtTime(450, now + 0.1);
osc.frequency.setValueAtTime(600, now + 0.2);
gainNode.gain.setValueAtTime(0.2, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.4);
osc.start(now);
osc.stop(now + 0.4);
} else if (type === 'lose') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, now);
osc.frequency.linearRampToValueAtTime(80, now + 0.4);
gainNode.gain.setValueAtTime(0.2, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.4);
osc.start(now);
osc.stop(now + 0.4);
}
}
// --- Variabel Game ---
let gold = 50;
let baseHp = 100;
let enemyBaseHp = 100;
let units = [];
let isGameOver = false;
let gameInterval;
let enemySpawnTimer = 0;
const unitTypes = {
miner: { hp: 30, atk: 2, spd: 0.8, range: 20, cost: 25, label: '⛏️' },
swordsman: { hp: 50, atk: 8, spd: 1.2, range: 25, cost: 20, label: '⚔️' },
archer: { hp: 35, atk: 10, spd: 1.0, range: 120, cost: 30, label: '🏹' },
knight: { hp: 120, atk: 15, spd: 0.7, range: 30, cost: 50, label: '🛡️' }
};
// --- Fungsi Utama Game ---
function spawnMiner() {
if (gold >= 25 && !isGameOver) {
gold -= 25;
createUnit('miner', 'player');
playSound('spawn');
updateUI();
}
}
function spawnUnit(type, cost) {
if (gold >= cost && !isGameOver) {
gold -= cost;
createUnit(type, 'player');
playSound('spawn');
updateUI();
}
}
function createUnit(type, team) {
let stats = unitTypes[type];
let startX = team === 'player' ? 60 : window.innerWidth > 600 ? 500 : window.innerHeight > window.innerWidth ? window.innerWidth - 80 : 500;
// Batasi kustomisasi posisi awal jika layar kecil
let battlefieldWidth = document.getElementById('battlefield').clientWidth;
if (team === 'enemy') startX = battlefieldWidth - 80;
if (team === 'player') startX = 60;
let unitObj = {
type: type,
team: team,
hp: stats.hp,
maxHp: stats.hp,
atk: stats.atk,
spd: stats.spd,
range: stats.range,
label: stats.label,
x: startX,
element: document.createElement('div')
};
unitObj.element.className = `unit ${team === 'player' ? (type === 'miner' ? 'miner' : 'player-unit') : 'enemy-unit'}`;
unitObj.element.innerHTML = `${unitObj.label}<span style="font-size:8px">${unitObj.hp}</span>`;
unitObj.element.style.left = unitObj.x + 'px';
document.getElementById('battlefield').appendChild(unitObj.element);
units.push(unitObj);
}
// AI Musuh membuat unit secara otomatis seiring waktu
function handleEnemyAI(dt) {
enemySpawnTimer += dt;
if (enemySpawnTimer > 4) { // Musuh spawn setiap 4 detik
enemySpawnTimer = 0;
let choices = ['swordsman', 'archer', 'knight'];
let randomChoice = choices[Math.floor(Math.random() * choices.length)];
createUnit(randomChoice, 'enemy');
}
}
// Loop Utama Game (Dipanggil setiap frame / ~60fps)
let lastTime = performance.now();
function gameLoop(time) {
if (isGameOver) return;
let dt = (time - lastTime) / 1000;
lastTime = time;
handleEnemyAI(dt);
let battlefieldWidth = document.getElementById('battlefield').clientWidth;
// Logika pergerakan & pertarungan unit
for (let i = units.length - 1; i >= 0; i--) {
let u = units[i];
// Penambang khusus: Jika milik player dan berada di dekat markas, menghasilkan emas
if (u.team === 'player' && u.type === 'miner' && u.x < 100) {
if (Math.random() < 0.02) { // Mendapatkan emas secara berkala
gold += 2;
playSound('gold');
updateUI();
}
}
// Cari target terdekat
let target = null;
let minDst = 9999;
for (let j = 0; j < units.length; j++) {
let other = units[j];
if (u.team !== other.team) {
let dst = Math.abs(u.x - other.x);
if (dst < minDst) {
minDst = dst;
target = other;
}
}
}
// Cek jarak dengan markas musuh/lawan
let enemyBaseX = battlefieldWidth - 70;
let playerBaseX = 50;
let baseTargetX = u.team === 'player' ? enemyBaseX : playerBaseX;
let baseDst = Math.abs(u.x - baseTargetX);
// Pergerakan atau Pertarungan
if (target && minDst <= u.range) {
// Serang unit lawan
if (Math.random() < 0.05) { // Kecepatan serang
target.hp -= u.atk;
playSound('hit');
if (target.hp <= 0) {
target.element.remove();
units = units.filter(item => item !== target);
}
}
} else if (baseDst <= u.range) {
// Serang markas lawan
if (Math.random() < 0.05) {
if (u.team === 'player') {
enemyBaseHp -= u.atk;
if (enemyBaseHp < 0) enemyBaseHp = 0;
} else {
baseHp -= u.atk;
if (baseHp < 0) baseHp = 0;
}
playSound('hit');
updateUI();
checkWinCondition();
}
} else {
// Bergerak maju
if (u.team === 'player') {
u.x += u.spd;
// Cegah menumpuk terlalu dekat dengan unit di depannya
let blocked = units.some(o => o.team === 'player' && o !== u && o.x > u.x && o.x - u.x < 30);
if (blocked) u.x -= u.spd;
} else {
u.x -= u.spd;
let blocked = units.some(o => o.team === 'enemy' && o !== u && o.x < u.x && u.x - o.x < 30);
if (blocked) u.x += u.spd;
}
}
// Update posisi elemen HTML
u.element.style.left = u.x + 'px';
u.element.querySelector('span').innerText = Math.max(0, Math.floor(u.hp));
}
// Pasif Emas seiring waktu untuk player
if (Math.random() < 0.015) {
gold += 1;
updateUI();
}
requestAnimationFrame(gameLoop);
}
function updateUI() {
document.getElementById('gold-display').innerText = gold;
document.getElementById('base-hp').innerText = baseHp;
document.getElementById('p-hp-bar').innerText = baseHp;
document.getElementById('e-hp-bar').innerText = enemyBaseHp;
let minerCount = units.filter(u => u.team === 'player' && u.type === 'miner').length;
document.getElementById('miner-count').innerText = minerCount;
}
function checkWinCondition() {
if (baseHp <= 0) {
isGameOver = true;
playSound('lose');
document.getElementById('game-message').innerText = "KAMU KALAH!\nMarkas hancur.";
document.getElementById('message-overlay').style.display = 'flex';
} else if (enemyBaseHp <= 0) {
isGameOver = true;
playSound('win');
document.getElementById('game-message').innerText = "KEMENANGAN!\nMusuh dikalahkan.";
document.getElementById('message-overlay').style.display = 'flex';
}
}
function resetGame() {
units.forEach(u => u.element.remove());
units = [];
gold = 50;
baseHp = 100;
enemyBaseHp = 100;
isGameOver = false;
enemySpawnTimer = 0;
document.getElementById('message-overlay').style.display = 'none';
updateUI();
lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
// Inisialisasi awal
updateUI();
requestAnimationFrame(gameLoop);
</script>
</body>
</html>Game Source: Pertarungan Abad Pertengahan
Creator: CosmicOtter50
Libraries: none
Complexity: complex (501 lines, 16.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: pertarungan-abad-pertengahan-cosmicotter50" to link back to the original. Then publish at arcadelab.ai/publish.