| <!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> |
| |
| 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 |
| }; |
| |
| |
| 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'); |
| |
| |
| 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'); |
| |
| |
| function initGame() { |
| |
| updatePlayerPosition(); |
| updateHealthBar(); |
| |
| |
| generateWorld(); |
| |
| |
| setupControls(); |
| |
| |
| requestAnimationFrame(gameLoop); |
| |
| |
| 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!" |
| ]); |
| } |
| |
| |
| function generateWorld() { |
| |
| createBuilding(300, 200, 150, 200); |
| createBuilding(600, 250, 200, 150); |
| createBuilding(800, 400, 120, 180); |
| |
| |
| createMarketStall(500, 500); |
| createMarketStall(550, 500); |
| createMarketStall(500, 550); |
| createMarketStall(550, 550); |
| |
| |
| createPalmTree(200, 400); |
| createPalmTree(250, 450); |
| createPalmTree(700, 300); |
| createPalmTree(900, 500); |
| |
| |
| 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." |
| ]); |
| |
| |
| createEnemy(700, 700); |
| createEnemy(750, 650); |
| createEnemy(800, 700); |
| } |
| |
| |
| 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 }); |
| } |
| |
| |
| 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 }); |
| } |
| |
| |
| 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 }); |
| } |
| |
| |
| function createNPC(x, y, type, dialog) { |
| const npc = document.createElement('div'); |
| npc.className = 'npc'; |
| |
| |
| 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); |
| } |
| }); |
| |
| |
| npc.addEventListener('click', function() { |
| gameState.npcs.find(n => n.element === this).interact(); |
| }); |
| } |
| |
| |
| 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() { |
| |
| 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`; |
| } |
| |
| |
| 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(); |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| function updatePlayerPosition() { |
| playerElement.style.left = `${gameState.player.x}px`; |
| playerElement.style.top = `${gameState.player.y}px`; |
| |
| |
| 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`; |
| } |
| |
| |
| 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'; |
| } |
| } |
| |
| |
| function showDialog(messages) { |
| gameState.currentDialog = messages; |
| gameState.dialogIndex = 0; |
| dialogText.textContent = messages[0]; |
| dialogBox.style.display = 'block'; |
| } |
| |
| |
| function nextDialog() { |
| gameState.dialogIndex++; |
| if (gameState.dialogIndex < gameState.currentDialog.length) { |
| dialogText.textContent = gameState.currentDialog[gameState.dialogIndex]; |
| } else { |
| dialogBox.style.display = 'none'; |
| } |
| } |
| |
| |
| function playerAttack() { |
| |
| const attack = document.createElement('div'); |
| attack.className = 'attack-animation'; |
| |
| |
| 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); |
| |
| |
| setTimeout(() => { |
| attack.remove(); |
| }, 500); |
| |
| |
| 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.element.remove(); |
| gameState.enemies.splice(index, 1); |
| |
| |
| 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." |
| ]); |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| function setupControls() { |
| |
| 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; |
| } |
| }); |
| |
| |
| 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'; |
| }); |
| |
| |
| [upBtn, downBtn, leftBtn, rightBtn].forEach(btn => { |
| btn.addEventListener('touchend', (e) => { |
| e.preventDefault(); |
| gameState.player.moving = false; |
| }); |
| }); |
| |
| |
| dialogNext.addEventListener('click', nextDialog); |
| } |
| |
| |
| function gameLoop() { |
| |
| 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(); |
| } |
| |
| |
| gameState.enemies.forEach(enemy => enemy.update()); |
| |
| |
| 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); |
| } |
| |
| |
| 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> |