🎮ArcadeLab

Medieval Warrior

by StormBear97
522 lines12.5 KB
▶ Play
<!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>Medieval Warrior</title>
<style>
* {
  box-sizing: border-box;
  -webkit-user-select: none;
  user-select: none;
  touch-action: none;
}
body {
  margin: 0;
  overflow: hidden;
  background: #111;
  font-family: Georgia, serif;
  color: white;
}
#game {
  position: relative;
  width: 100vw;
  height: 100vh;
  max-width: 900px;
  margin: auto;
  overflow: hidden;
  background:
    linear-gradient(rgba(30,20,10,.25), rgba(0,0,0,.55)),
    linear-gradient(#6687a1 0 48%, #405a2d 49% 72%, #292014 73%);
}
#game:before {
  content: "";
  position: absolute;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background: #ffd66b;
  top: 70px;
  right: 12%;
  box-shadow: 0 0 50px #ffb52e;
}
.ground {
  position: absolute;
  bottom: 0;
  height: 28%;
  width: 100%;
  background:
    repeating-linear-gradient(
      100deg,
      #342618 0px,
      #342618 12px,
      #3f2d1c 13px,
      #3f2d1c 25px
    );
  border-top: 5px solid #21180f;
}
#hud {
  position: absolute;
  z-index: 20;
  top: 12px;
  left: 12px;
  right: 12px;
  display: flex;
  justify-content: space-between;
  font-weight: bold;
  text-shadow: 2px 2px #000;
}
.bar {
  width: 130px;
  height: 18px;
  background: #300;
  border: 2px solid white;
  border-radius: 10px;
  overflow: hidden;
}
.hp {
  height: 100%;
  width: 100%;
  background: linear-gradient(#55ff55,#168b16);
}

/* ===== KARAKTER - BENTUK BARU SESUAI GAMBAR ===== */
.character {
  position: absolute;
  z-index: 10;
  width: 84px;
  height: 128px;
  bottom: 27%;
}
#player { left: 18%; }
#enemy { right: 18%; }

.shadow {
  position: absolute;
  bottom: -2px;
  left: 50%;
  transform: translateX(-50%);
  width: 72px;
  height: 16px;
  background: rgba(0,0,0,.45);
  border-radius: 50%;
  z-index: 0;
}

/* Pedang putih vertikal di belakang badan - seperti di gambar */
.sword-char {
  position: absolute;
  width: 10px;
  height: 78px;
  background: linear-gradient(#ffffff, #d7dce3);
  border: 1px solid #8a8f98;
  border-radius: 3px;
  top: -6px;
  z-index: 1;
}
#player .sword-char {
  right: -2px;
  transform: rotate(7deg);
}
#enemy .sword-char {
  left: -2px;
  transform: rotate(-7deg);
}
/* gagang pedang */
.sword-char:after {
  content: "";
  position: absolute;
  width: 16px;
  height: 8px;
  background: #3a2a1a;
  border-radius: 3px;
  bottom: -6px;
  left: -3px;
}

/* Kaki - kecil, gelap, seperti di gambar */
.leg {
  position: absolute;
  width: 18px;
  height: 30px;
  background: #1a0f0a;
  bottom: 4px;
  border-radius: 0 0 9px 9px;
  z-index: 1;
}
.leg.l { left: 20px; }
.leg.r { right: 20px; }

/* Badan */
.body-char {
  position: absolute;
  top: 46px;
  left: 50%;
  transform: translateX(-50%);
  width: 62px;
  height: 60px;
  border-radius: 16px 16px 10px 10px;
  z-index: 2;
}
.tunic-inner {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 7px;
  width: 26px;
  height: 46px;
  border-radius: 8px;
  z-index: 2;
}
/* Bantalan bahu bulat abu-abu */
.shoulder {
  position: absolute;
  width: 29px;
  height: 29px;
  background: #c9ced6;
  border: 2px solid #8a8f98;
  border-radius: 50%;
  top: -3px;
  z-index: 3;
  box-shadow: inset -3px -3px 0 rgba(0,0,0,.12);
}
.shoulder.left { left: -18px; }
.shoulder.right { right: -18px; }

/* ===== PEMAIN - Gambar 1 ===== */
#player .body-char {
  background: #1e3a8a;
  border: 3px solid #0f1e4a;
}
#player .tunic-inner {
  background: #3aa0ff;
  box-shadow: inset 0 -6px 0 rgba(0,0,0,.08);
}
/* Perisai / bulatan oranye di sisi kanan pemain */
#player .shield {
  position: absolute;
  width: 27px;
  height: 27px;
  background: #f07f1a;
  border: 3px solid #b85a0a;
  border-radius: 50%;
  right: -17px;
  top: 16px;
  z-index: 4;
}

/* Kepala Pemain */
.head-char {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 54px;
  height: 50px;
  z-index: 3;
}
.face {
  position: absolute;
  width: 44px;
  height: 38px;
  background: #eab886;
  border-radius: 12px 12px 18px 18px;
  top: 12px;
  left: 5px;
  z-index: 2;
}
#player .helmet {
  position: absolute;
  width: 56px;
  height: 32px;
  background: #9aa0a8;
  border: 2px solid #6b7078;
  border-radius: 28px 28px 8px 8px;
  top: 0;
  left: -1px;
  z-index: 3;
  box-shadow: inset -7px -5px 0 rgba(0,0,0,.15);
}
#player .visor {
  position: absolute;
  width: 37px;
  height: 9px;
  background: #111111;
  border-radius: 4px;
  top: 23px;
  left: 9px;
  z-index: 4;
}

/* ===== MUSUH - Gambar 2 ===== */
#enemy .body-char {
  background: #6b1a1a;
  border: 3px solid #3a0d0d;
}
#enemy .tunic-inner {
  background: #e83232;
  box-shadow: inset 0 -6px 0 rgba(0,0,0,.08);
}
#enemy .hair {
  position: absolute;
  width: 54px;
  height: 22px;
  background: #8b5a2b;
  border-radius: 20px 20px 4px 4px;
  top: 0;
  left: 0;
  z-index: 3;
  box-shadow: inset -6px -4px 0 rgba(0,0,0,.15);
}
#enemy .blindfold {
  position: absolute;
  width: 58px;
  height: 16px;
  background: #c1272d;
  border: 2px solid #7a1414;
  border-radius: 4px;
  top: 16px;
  left: -2px;
  z-index: 4;
}

/* Animasi serangan */
.attack { animation: attack .25s ease; }
@keyframes attack {
  50% { transform: translateX(35px) rotate(-8deg); }
}
.enemyAttack { animation: enemyAttack .25s ease; }
@keyframes enemyAttack {
  50% { transform: translateX(-35px); }
}
.hit { animation: hit .25s; }
@keyframes hit {
  50% { filter: brightness(4); transform: translateX(8px); }
}

/* Tombol */
.controls {
  position: absolute;
  z-index: 30;
  bottom: 20px;
  left: 0;
  right: 0;
  display: flex;
  justify-content: space-around;
  align-items: center;
}
button {
  border: 3px solid #c79b50;
  background: linear-gradient(#694622,#2d1a0d);
  color: white;
  font-size: 18px;
  font-weight: bold;
  border-radius: 50%;
  width: 72px;
  height: 72px;
  box-shadow: 0 5px 0 #170d06;
}
button:active {
  transform: translateY(4px);
  box-shadow: 0 1px 0 #170d06;
}
#attack {
  width: 100px;
  height: 100px;
  background: radial-gradient(#e44325,#7b160d);
  border-color: #ffcf55;
}
#startScreen,
#gameOver {
  position: absolute;
  z-index: 100;
  inset: 0;
  background: rgba(0,0,0,.85);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
}
#startScreen h1,
#gameOver h1 {
  color: #e8b85c;
  font-size: 38px;
  text-shadow: 3px 3px #000;
}
.startBtn {
  border-radius: 15px;
  width: 190px;
  height: 60px;
  margin-top: 20px;
}
#message {
  position: absolute;
  z-index: 40;
  top: 30%;
  width: 100%;
  text-align: center;
  font-size: 32px;
  color: #ffd35a;
  text-shadow: 3px 3px #000;
  pointer-events: none;
}
</style>
</head>
<body>
<div id="game">
  <div id="hud">
    <div>
      ⚔ KSATRIA
      <div class="bar"><div id="playerHP" class="hp"></div></div>
    </div>
    <div>🏆 <span id="score">0</span></div>
    <div>
      👹 MUSUH
      <div class="bar"><div id="enemyHP" class="hp"></div></div>
    </div>
  </div>
  <div class="ground"></div>

  <!-- PEMAIN - sesuai gambar 1 -->
  <div id="player" class="character">
    <div class="shadow"></div>
    <div class="sword-char"></div>
    <div class="leg l"></div>
    <div class="leg r"></div>
    <div class="body-char">
      <div class="tunic-inner"></div>
      <div class="shoulder left"></div>
      <div class="shoulder right"></div>
      <div class="shield"></div>
    </div>
    <div class="head-char">
      <div class="face"></div>
      <div class="helmet"></div>
      <div class="visor"></div>
    </div>
  </div>

  <!-- MUSUH - sesuai gambar 2 -->
  <div id="enemy" class="character">
    <div class="shadow"></div>
    <div class="sword-char"></div>
    <div class="leg l"></div>
    <div class="leg r"></div>
    <div class="body-char">
      <div class="tunic-inner"></div>
      <div class="shoulder left"></div>
      <div class="shoulder right"></div>
    </div>
    <div class="head-char">
      <div class="face"></div>
      <div class="hair"></div>
      <div class="blindfold"></div>
    </div>
  </div>

  <div id="message"></div>
  <div class="controls">
    <button id="left">◀</button>
    <button id="attack">⚔<br>SERANG</button>
    <button id="right">▶</button>
  </div>
  <div id="startScreen">
    <h1>⚔ MEDIEVAL<br>WARRIOR</h1>
    <p>Hadapi para ksatria musuh!</p>
    <button class="startBtn" id="start">MULAI PERANG</button>
  </div>
  <div id="gameOver" style="display:none">
    <h1 id="result">KALAH!</h1>
    <p>Skor: <span id="finalScore">0</span></p>
    <button class="startBtn" onclick="location.reload()">MAIN LAGI</button>
  </div>
</div>
<script>
let audioCtx;
function initAudio() { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
function sound(type) {
  if (!audioCtx) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.connect(gain); gain.connect(audioCtx.destination);
  let freq = 200; let duration = .15;
  if (type === "sword") { freq = 180; duration = .12; osc.type = "sawtooth"; }
  if (type === "hit") { freq = 90; duration = .18; osc.type = "square"; }
  if (type === "win") { freq = 600; duration = .5; osc.type = "triangle"; }
  if (type === "lose") { freq = 100; duration = .5; osc.type = "sawtooth"; }
  osc.frequency.value = freq;
  gain.gain.setValueAtTime(.15, audioCtx.currentTime);
  gain.gain.exponentialRampToValueAtTime(.001, audioCtx.currentTime + duration);
  osc.start(); osc.stop(audioCtx.currentTime + duration);
}
const player = document.getElementById("player");
const enemy = document.getElementById("enemy");
const playerHPBar = document.getElementById("playerHP");
const enemyHPBar = document.getElementById("enemyHP");
let playerHP = 100; let enemyHP = 100; let score = 0; let playing = false;
let playerX = 18; let enemyX = 72;
function updatePosition() { player.style.left = playerX + "%"; enemy.style.right = (100 - enemyX) + "%"; }
function attack() {
  if (!playing) return;
  player.classList.remove("attack"); void player.offsetWidth; player.classList.add("attack");
  sound("sword");
  if (Math.abs(playerX - enemyX) < 32) {
    enemyHP -= 20; if (enemyHP < 0) enemyHP = 0;
    enemyHPBar.style.width = enemyHP + "%";
    enemy.classList.add("hit"); sound("hit");
    setTimeout(() => { enemy.classList.remove("hit"); }, 250);
    if (enemyHP <= 0) win();
  }
}
function enemyAttack() {
  if (!playing) return;
  if (Math.abs(playerX - enemyX) < 32) {
    enemy.classList.remove("enemyAttack"); void enemy.offsetWidth; enemy.classList.add("enemyAttack");
    playerHP -= 10; if (playerHP < 0) playerHP = 0;
    playerHPBar.style.width = playerHP + "%";
    player.classList.add("hit"); sound("hit");
    setTimeout(() => { player.classList.remove("hit"); }, 250);
    if (playerHP <= 0) lose();
  }
}
function moveLeft() { if (!playing) return; playerX -= 5; if (playerX < 2) playerX = 2; updatePosition(); }
function moveRight() { if (!playing) return; playerX += 5; if (playerX > 75) playerX = 75; updatePosition(); }
document.getElementById("left").addEventListener("touchstart", e => { e.preventDefault(); moveLeft(); });
document.getElementById("right").addEventListener("touchstart", e => { e.preventDefault(); moveRight(); });
document.getElementById("attack").addEventListener("touchstart", e => { e.preventDefault(); attack(); });
document.getElementById("left").onclick = moveLeft;
document.getElementById("right").onclick = moveRight;
document.getElementById("attack").onclick = attack;
document.addEventListener("keydown", e => {
  if (e.key === "ArrowLeft") moveLeft();
  if (e.key === "ArrowRight") moveRight();
  if (e.key === " " || e.key === "Enter") attack();
});
let enemyTimer;
function startEnemy() { enemyTimer = setInterval(() => { enemyAttack(); }, 1200); }
function win() {
  playing = false; clearInterval(enemyTimer); score += 100;
  document.getElementById("score").textContent = score;
  document.getElementById("finalScore").textContent = score;
  sound("win");
  document.getElementById("result").textContent = "🏆 MENANG!";
  document.getElementById("gameOver").style.display = "flex";
}
function lose() {
  playing = false; clearInterval(enemyTimer); sound("lose");
  document.getElementById("result").textContent = "💀 KALAH!";
  document.getElementById("finalScore").textContent = score;
  document.getElementById("gameOver").style.display = "flex";
}
document.getElementById("start").onclick = async function() {
  initAudio();
  if (audioCtx.state === "suspended") { await audioCtx.resume(); }
  sound("win");
  document.getElementById("startScreen").style.display = "none";
  playing = true; startEnemy();
};
</script>
</body>
</html>

Game Source: Medieval Warrior

Creator: StormBear97

Libraries: none

Complexity: complex (522 lines, 12.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: medieval-battle-out-become-enemy-stormbear97" to link back to the original. Then publish at arcadelab.ai/publish.