top-up / index.html
ido6969's picture
buatkan aku game rpg 2dimensi berlatar belakang di abad pertengahan di negara timur tengah, dengan pemeran utama laki laki - Follow Up Deployment
2d693bf verified
Raw
History Blame Contribute Delete
28.5 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Desert Warrior: Sands of Fate</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Almendra+SC&family=Cinzel+Decorative&display=swap');
body {
font-family: 'Almendra SC', serif;
background-color: #111;
color: #d4af37;
overflow: hidden;
touch-action: none;
user-select: none;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%231a120b"/><path d="M0,50 Q25,40 50,50 T100,50" stroke="%233d2b1f" fill="none" stroke-width="2"/><path d="M0,70 Q25,60 50,70 T100,70" stroke="%233d2b1f" fill="none" stroke-width="2"/><path d="M0,30 Q25,20 50,30 T100,30" stroke="%233d2b1f" fill="none" stroke-width="2"/></svg>');
background-size: cover;
overflow: hidden;
}
#player {
position: absolute;
width: 64px;
height: 64px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect x="24" y="40" width="16" height="24" fill="%233d2b1f"/><circle cx="32" cy="24" r="12" fill="%238B4513"/><rect x="20" y="20" width="24" height="20" fill="%23d4af37" rx="2"/><path d="M16,28 L8,36 M48,28 L56,36" stroke="%233d2b1f" stroke-width="4"/><path d="M32,24 L32,16" stroke="%233d2b1f" stroke-width="2"/></svg>');
z-index: 10;
transition: transform 0.2s;
}
.npc {
position: absolute;
width: 64px;
height: 64px;
background-size: contain;
z-index: 9;
}
.enemy {
position: absolute;
width: 64px;
height: 64px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect x="24" y="40" width="16" height="24" fill="%23222222"/><circle cx="32" cy="24" r="12" fill="%23333"/><rect x="20" y="20" width="24" height="20" fill="%23772222" rx="2"/><path d="M16,28 L8,36 M48,28 L56,36" stroke="%23222" stroke-width="4"/><path d="M32,24 L32,16" stroke="%23222" stroke-width="2" stroke-linecap="round"/></svg>');
z-index: 8;
}
.building {
position: absolute;
background-color: #3d2b1f;
border: 2px solid #d4af37;
z-index: 5;
}
.market-stall {
position: absolute;
background-color: #5e3c23;
border: 1px solid #d4af37;
z-index: 6;
}
.palm-tree {
position: absolute;
width: 48px;
height: 80px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="80" viewBox="0 0 48 80"><rect x="20" y="40" width="8" height="40" fill="%235e3c23"/><path d="M24,40 Q-10,20 24,0 Q50,20 24,40" fill="%23007040"/></svg>');
z-index: 7;
}
.dialog-box {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
width: 80%;
max-width: 600px;
background-color: rgba(26, 18, 11, 0.9);
border: 2px solid #d4af37;
padding: 15px;
border-radius: 5px;
z-index: 100;
display: none;
}
.health-bar {
position: absolute;
top: 10px;
left: 10px;
width: 200px;
height: 20px;
background-color: #3d2b1f;
border: 1px solid #d4af37;
z-index: 100;
}
.health-fill {
height: 100%;
width: 100%;
background-color: #a52a2a;
transition: width 0.3s;
}
.inventory {
position: absolute;
top: 10px;
right: 10px;
background-color: rgba(26, 18, 11, 0.8);
border: 1px solid #d4af37;
padding: 5px;
z-index: 100;
}
.item {
display: inline-block;
width: 32px;
height: 32px;
margin: 2px;
background-color: #3d2b1f;
border: 1px solid #d4af37;
text-align: center;
line-height: 30px;
}
.attack-animation {
position: absolute;
width: 40px;
height: 40px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><path d="M20,0 L40,20 L20,40 L0,20 Z" fill="%23d4af37" opacity="0.7"/></svg>');
background-size: contain;
z-index: 11;
animation: spin 0.5s linear;
}
@keyframes spin {
from { transform: rotate(0deg) scale(0.5); opacity: 1; }
to { transform: rotate(360deg) scale(1.5); opacity: 0; }
}
#controls {
position: absolute;
bottom: 20px;
right: 20px;
z-index: 100;
}
.control-btn {
width: 50px;
height: 50px;
background-color: rgba(26, 18, 11, 0.7);
border: 1px solid #d4af37;
color: #d4af37;
border-radius: 50%;
margin: 5px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
cursor: pointer;
}
#quest-log {
position: absolute;
top: 40px;
left: 10px;
background-color: rgba(26, 18, 11, 0.8);
border: 1px solid #d4af37;
padding: 5px;
max-width: 200px;
z-index: 100;
display: none;
}
#map {
position: absolute;
bottom: 20px;
left: 20px;
width: 150px;
height: 150px;
background-color: rgba(26, 18, 11, 0.7);
border: 1px solid #d4af37;
z-index: 100;
}
#map-player {
position: absolute;
width: 6px;
height: 6px;
background-color: #d4af37;
border-radius: 50%;
}
</style>
</head>
<body>
<div id="game-container">
<div id="player"></div>
<div class="health-bar">
<div id="health-fill" class="health-fill"></div>
</div>
<div class="inventory">
<div class="item">⚔️</div>
<div class="item">🛡️</div>
<div class="item">💊</div>
<div class="item">💰</div>
</div>
<div id="quest-log">
<h3 class="text-lg font-bold border-b border-amber-700 mb-2">Quests</h3>
<div id="quest-1" class="text-sm mb-1">✔️ Find the missing caravan</div>
<div id="quest-2" class="text-sm mb-1">❌ Defeat the desert raiders</div>
<div id="quest-3" class="text-sm">❌ Recover the stolen artifact</div>
</div>
<div id="map">
<div id="map-player"></div>
</div>
<div class="dialog-box" id="dialog-box">
<p id="dialog-text"></p>
<button id="dialog-next" class="mt-2 px-3 py-1 bg-amber-700 text-amber-100 rounded">Next</button>
</div>
<div id="controls" class="hidden md:flex">
<div class="flex flex-col items-center">
<div class="control-btn" id="up-btn"></div>
<div class="flex">
<div class="control-btn" id="left-btn"></div>
<div class="control-btn" id="down-btn"></div>
<div class="control-btn" id="right-btn"></div>
</div>
</div>
<div class="control-btn" id="attack-btn">⚔️</div>
<div class="control-btn" id="menu-btn"></div>
</div>
</div>
<script>
// Game state
const gameState = {
player: {
x: 400,
y: 300,
health: 100,
maxHealth: 100,
attackPower: 15,
direction: 'down',
moving: false,
inventory: ['sword', 'shield', 'potion', 'gold'],
quests: {
findCaravan: { completed: true },
defeatRaiders: { completed: false },
recoverArtifact: { completed: false }
}
},
npcs: [],
enemies: [],
buildings: [],
marketStalls: [],
palmTrees: [],
currentDialog: null,
dialogIndex: 0,
gameWidth: window.innerWidth,
gameHeight: window.innerHeight,
worldWidth: 2000,
worldHeight: 2000
};
// DOM elements
const playerElement = document.getElementById('player');
const healthFill = document.getElementById('health-fill');
const dialogBox = document.getElementById('dialog-box');
const dialogText = document.getElementById('dialog-text');
const dialogNext = document.getElementById('dialog-next');
const questLog = document.getElementById('quest-log');
const mapPlayer = document.getElementById('map-player');
// Control buttons
const upBtn = document.getElementById('up-btn');
const downBtn = document.getElementById('down-btn');
const leftBtn = document.getElementById('left-btn');
const rightBtn = document.getElementById('right-btn');
const attackBtn = document.getElementById('attack-btn');
const menuBtn = document.getElementById('menu-btn');
// Initialize game
function initGame() {
// Set player initial position
updatePlayerPosition();
updateHealthBar();
// Generate world
generateWorld();
// Set up controls
setupControls();
// Start game loop
requestAnimationFrame(gameLoop);
// Show initial dialog
showDialog([
"Welcome to Desert Warrior: Sands of Fate",
"You are Amir, a warrior from the ancient city of Al'Rashid.",
"The city has been attacked by desert raiders who stole a sacred artifact.",
"Your quest is to track down the raiders and recover the artifact.",
"Use the controls to move and attack enemies.",
"Good luck, warrior!"
]);
}
// Generate game world
function generateWorld() {
// Create buildings
createBuilding(300, 200, 150, 200); // Mosque
createBuilding(600, 250, 200, 150); // Palace
createBuilding(800, 400, 120, 180); // House
// Create market stalls
createMarketStall(500, 500);
createMarketStall(550, 500);
createMarketStall(500, 550);
createMarketStall(550, 550);
// Create palm trees
createPalmTree(200, 400);
createPalmTree(250, 450);
createPalmTree(700, 300);
createPalmTree(900, 500);
// Create NPCs
createNPC(520, 520, "merchant", [
"Welcome to my humble stall, warrior.",
"I sell the finest silks and spices in all of Al'Rashid.",
"Perhaps you'd like to browse my wares?"
]);
createNPC(350, 250, "imam", [
"Peace be upon you, my son.",
"The raiders have desecrated our sacred mosque.",
"May Allah guide you in your quest for justice."
]);
// Create enemies
createEnemy(700, 700);
createEnemy(750, 650);
createEnemy(800, 700);
}
// Create a building
function createBuilding(x, y, width, height) {
const building = document.createElement('div');
building.className = 'building';
building.style.left = `${x}px`;
building.style.top = `${y}px`;
building.style.width = `${width}px`;
building.style.height = `${height}px`;
document.getElementById('game-container').appendChild(building);
gameState.buildings.push({ x, y, width, height, element: building });
}
// Create a market stall
function createMarketStall(x, y) {
const stall = document.createElement('div');
stall.className = 'market-stall';
stall.style.left = `${x}px`;
stall.style.top = `${y}px`;
stall.style.width = '40px';
stall.style.height = '40px';
document.getElementById('game-container').appendChild(stall);
gameState.marketStalls.push({ x, y, element: stall });
}
// Create a palm tree
function createPalmTree(x, y) {
const tree = document.createElement('div');
tree.className = 'palm-tree';
tree.style.left = `${x}px`;
tree.style.top = `${y}px`;
document.getElementById('game-container').appendChild(tree);
gameState.palmTrees.push({ x, y, element: tree });
}
// Create an NPC
function createNPC(x, y, type, dialog) {
const npc = document.createElement('div');
npc.className = 'npc';
// Different visuals for different NPC types
if (type === 'merchant') {
npc.style.backgroundImage = 'url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect x="24" y="40" width="16" height="24" fill="%23404080"/><circle cx="32" cy="24" r="12" fill="%238080c0"/><rect x="20" y="20" width="24" height="20" fill="%238080c0" rx="2"/><path d="M16,28 L8,36 M48,28 L56,36" stroke="%23404080" stroke-width="4"/><path d="M32,24 L32,16" stroke="%23404080" stroke-width="2"/></svg>\')';
} else if (type === 'imam') {
npc.style.backgroundImage = 'url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64"><rect x="24" y="40" width="16" height="24" fill="%23ffffff"/><circle cx="32" cy="24" r="12" fill="%23ffffff"/><rect x="20" y="20" width="24" height="20" fill="%23ffffff" rx="2"/><path d="M16,28 L8,36 M48,28 L56,36" stroke="%23ffffff" stroke-width="4"/><path d="M32,24 L32,16" stroke="%23ffffff" stroke-width="2"/></svg>\')';
}
npc.style.left = `${x}px`;
npc.style.top = `${y}px`;
document.getElementById('game-container').appendChild(npc);
gameState.npcs.push({
x,
y,
type,
dialog,
element: npc,
interact: function() {
showDialog(this.dialog);
}
});
// Make NPC clickable
npc.addEventListener('click', function() {
gameState.npcs.find(n => n.element === this).interact();
});
}
// Create an enemy
function createEnemy(x, y) {
const enemy = document.createElement('div');
enemy.className = 'enemy';
enemy.style.left = `${x}px`;
enemy.style.top = `${y}px`;
document.getElementById('game-container').appendChild(enemy);
gameState.enemies.push({
x,
y,
health: 30,
element: enemy,
update: function() {
// Simple AI: move toward player if within 300px
const dx = gameState.player.x - this.x;
const dy = gameState.player.y - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 300 && distance > 50) {
this.x += dx / distance * 2;
this.y += dy / distance * 2;
this.element.style.left = `${this.x}px`;
this.element.style.top = `${this.y}px`;
}
// Check collision with player
if (distance < 40 && Math.random() < 0.02) {
gameState.player.health -= 5;
updateHealthBar();
if (gameState.player.health <= 0) {
showDialog(["You have been defeated!", "The raiders have overpowered you.", "Press OK to restart the game."]);
gameState.player.health = gameState.player.maxHealth;
updateHealthBar();
}
}
}
});
}
// Update player position on screen
function updatePlayerPosition() {
playerElement.style.left = `${gameState.player.x}px`;
playerElement.style.top = `${gameState.player.y}px`;
// Update minimap player position
const mapX = (gameState.player.x / gameState.worldWidth) * 150;
const mapY = (gameState.player.y / gameState.worldHeight) * 150;
mapPlayer.style.left = `${mapX}px`;
mapPlayer.style.top = `${mapY}px`;
}
// Update health bar
function updateHealthBar() {
const healthPercent = (gameState.player.health / gameState.player.maxHealth) * 100;
healthFill.style.width = `${healthPercent}%`;
if (healthPercent < 30) {
healthFill.style.backgroundColor = '#ff0000';
} else if (healthPercent < 70) {
healthFill.style.backgroundColor = '#ffa500';
} else {
healthFill.style.backgroundColor = '#a52a2a';
}
}
// Show dialog
function showDialog(messages) {
gameState.currentDialog = messages;
gameState.dialogIndex = 0;
dialogText.textContent = messages[0];
dialogBox.style.display = 'block';
}
// Next dialog
function nextDialog() {
gameState.dialogIndex++;
if (gameState.dialogIndex < gameState.currentDialog.length) {
dialogText.textContent = gameState.currentDialog[gameState.dialogIndex];
} else {
dialogBox.style.display = 'none';
}
}
// Player attack
function playerAttack() {
// Create attack animation
const attack = document.createElement('div');
attack.className = 'attack-animation';
// Position based on player direction
let attackX = gameState.player.x;
let attackY = gameState.player.y;
switch (gameState.player.direction) {
case 'up':
attackY -= 40;
break;
case 'down':
attackY += 40;
break;
case 'left':
attackX -= 40;
break;
case 'right':
attackX += 40;
break;
}
attack.style.left = `${attackX}px`;
attack.style.top = `${attackY}px`;
document.getElementById('game-container').appendChild(attack);
// Remove after animation
setTimeout(() => {
attack.remove();
}, 500);
// Check for enemy hits
gameState.enemies.forEach((enemy, index) => {
const dx = enemy.x - attackX;
const dy = enemy.y - attackY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 40) {
enemy.health -= gameState.player.attackPower;
if (enemy.health <= 0) {
// Enemy defeated
enemy.element.remove();
gameState.enemies.splice(index, 1);
// Check if all enemies defeated
if (gameState.enemies.length === 0) {
gameState.player.quests.defeatRaiders.completed = true;
document.getElementById('quest-2').textContent = '✔️ Defeat the desert raiders';
showDialog([
"You have defeated all the raiders!",
"Now search for the stolen artifact in their camp."
]);
}
}
}
});
}
// Set up controls
function setupControls() {
// Keyboard controls
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowUp':
case 'w':
gameState.player.direction = 'up';
gameState.player.moving = true;
break;
case 'ArrowDown':
case 's':
gameState.player.direction = 'down';
gameState.player.moving = true;
break;
case 'ArrowLeft':
case 'a':
gameState.player.direction = 'left';
gameState.player.moving = true;
break;
case 'ArrowRight':
case 'd':
gameState.player.direction = 'right';
gameState.player.moving = true;
break;
case ' ':
playerAttack();
break;
case 'm':
document.getElementById('map').style.display =
document.getElementById('map').style.display === 'none' ? 'block' : 'none';
break;
case 'q':
questLog.style.display = questLog.style.display === 'none' ? 'block' : 'none';
break;
}
});
document.addEventListener('keyup', (e) => {
switch (e.key) {
case 'ArrowUp':
case 'w':
case 'ArrowDown':
case 's':
case 'ArrowLeft':
case 'a':
case 'ArrowRight':
case 'd':
gameState.player.moving = false;
break;
}
});
// Touch controls
upBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
gameState.player.direction = 'up';
gameState.player.moving = true;
});
downBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
gameState.player.direction = 'down';
gameState.player.moving = true;
});
leftBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
gameState.player.direction = 'left';
gameState.player.moving = true;
});
rightBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
gameState.player.direction = 'right';
gameState.player.moving = true;
});
attackBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
playerAttack();
});
menuBtn.addEventListener('touchstart', (e) => {
e.preventDefault();
questLog.style.display = questLog.style.display === 'none' ? 'block' : 'none';
});
// Touch end events
[upBtn, downBtn, leftBtn, rightBtn].forEach(btn => {
btn.addEventListener('touchend', (e) => {
e.preventDefault();
gameState.player.moving = false;
});
});
// Dialog next button
dialogNext.addEventListener('click', nextDialog);
}
// Game loop
function gameLoop() {
// Move player if moving
if (gameState.player.moving) {
const speed = 3;
switch (gameState.player.direction) {
case 'up':
gameState.player.y = Math.max(0, gameState.player.y - speed);
break;
case 'down':
gameState.player.y = Math.min(gameState.worldHeight - 64, gameState.player.y + speed);
break;
case 'left':
gameState.player.x = Math.max(0, gameState.player.x - speed);
break;
case 'right':
gameState.player.x = Math.min(gameState.worldWidth - 64, gameState.player.x + speed);
break;
}
updatePlayerPosition();
}
// Update enemies
gameState.enemies.forEach(enemy => enemy.update());
// Check for NPC interactions
if (gameState.player.moving) {
gameState.npcs.forEach(npc => {
const dx = npc.x - gameState.player.x;
const dy = npc.y - gameState.player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60 && Math.random() < 0.01) {
npc.interact();
}
});
}
requestAnimationFrame(gameLoop);
}
// Start the game when window loads
window.addEventListener('load', initGame);
//
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ido6969/top-up" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>