ok / index.html
sonusahani's picture
undefined - Initial Deployment
a09f8bb verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RPG Adventure</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom styles that can't be done with Tailwind */
.game-container {
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
}
.character {
transition: transform 0.2s;
}
.character.moving {
animation: walk 0.4s steps(4) infinite;
}
@keyframes walk {
from { background-position: 0 0; }
to { background-position: -128px 0; }
}
.dialog-box {
box-shadow: 0 0 0 4px #2d1a12, 0 0 0 8px #e8c39e;
}
.menu-box {
box-shadow: 0 0 0 4px #2d1a12, 0 0 0 8px #e8c39e;
}
.health-bar {
box-shadow: 0 0 0 2px #2d1a12;
}
.battle-arena {
background: linear-gradient(to bottom, #7eb8da, #a8d8f0);
}
</style>
</head>
<body class="bg-gray-900 text-white flex items-center justify-center min-h-screen">
<div class="relative w-full max-w-4xl h-[600px] bg-gray-800 overflow-hidden game-container">
<!-- Game Map -->
<div id="game-map" class="relative w-full h-full bg-green-700 overflow-hidden">
<!-- Map tiles will be rendered here -->
<div id="map-tiles" class="absolute top-0 left-0 w-full h-full"></div>
<!-- Player Character -->
<div id="player" class="character absolute w-8 h-8 bg-red-500 z-10"
style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iI2ZmMDAwMCIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PHBhdGggZmlsbD0iI2ZmODAwMCIgZD0iTTE2IDhjLTIuMjA5IDAtNCAxLjc5MS00IDRzMS43OTEgNCA0IDQgNC0xLjc5MSA0LTRzLTEuNzkxLTQtNC00eiIvPjwvc3ZnPg=='); background-size: 128px 32px;">
</div>
<!-- NPCs -->
<div id="npc1" class="character absolute w-8 h-8 bg-blue-500"
style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iIzAwMDBmZiIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PHBhdGggZmlsbD0iIzAwODBmZiIgZD0iTTE2IDhjLTIuMjA5IDAtNCAxLjc5MS00IDRzMS43OTEgNCA0IDQgNC0xLjc5MSA0LTRzLTEuNzkxLTQtNC00eiIvPjwvc3ZnPg=='); background-size: 128px 32px;"></div>
<div id="npc2" class="character absolute w-8 h-8 bg-green-500"
style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iIzAwZmYwMCIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PHBhdGggZmlsbD0iIzAwODAwMCIgZD0iTTE2IDhjLTIuMjA5IDAtNCAxLjc5MS00IDRzMS43OTEgNCA0IDQgNC0xLjc5MSA0LTRzLTEuNzkxLTQtNC00eiIvPjwvc3ZnPg=='); background-size: 128px 32px;"></div>
<!-- Items -->
<div id="chest" class="absolute w-8 h-8"
style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iI2ZmYzYwMCIgZD0iTTI4IDRoLTI0Yy0yLjIwOSAwLTQgMS43OTEtNCA0djIwYzAgMi4yMDkgMS43OTEgNCA0IDRoMjRjMi4yMDkgMCA0LTEuNzkxIDQtNHYtMjBjMC0yLjIwOS0xLjc5MS00LTQtNHoiLz48cGF0aCBmaWxsPSIjY2M4YzAwIiBkPSJNMTYgMTBjLTEuMTA1IDAtMiAuODk1LTIgMnYxMGMwIDEuMTA1Ljg5NSAyIDIgMnMyLS44OTUgMi0ydi0xMGMwLTEuMTA1LS44OTUtMi0yLTJ6Ii8+PC9zdmc+'); background-size: 32px 32px;"></div>
</div>
<!-- Dialog Box -->
<div id="dialog-box" class="dialog-box absolute bottom-4 left-4 right-4 bg-amber-100 text-gray-900 p-4 hidden">
<div class="flex items-start">
<div id="dialog-portrait" class="w-16 h-16 bg-gray-300 mr-4" style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iIzAwMDBmZiIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PHBhdGggZmlsbD0iIzAwODBmZiIgZD0iTTE2IDhjLTIuMjA5IDAtNCAxLjc5MS00IDRzMS43OTEgNCA0IDQgNC0xLjc5MSA0LTRzLTEuNzkxLTQtNC00eiIvPjwvc3ZnPg=='); background-size: 64px 64px;"></div>
<div class="flex-1">
<div id="dialog-name" class="font-bold text-lg mb-2">NPC</div>
<div id="dialog-text" class="text-sm">Hello there! Welcome to our village.</div>
</div>
</div>
<div id="dialog-next" class="text-right mt-2 text-sm font-bold cursor-pointer">Next <i class="fas fa-chevron-right"></i></div>
</div>
<!-- Menu System -->
<div id="menu" class="menu-box absolute top-0 left-0 w-full h-full bg-amber-100 text-gray-900 hidden">
<div class="p-4">
<h2 class="text-2xl font-bold mb-6 text-center">Menu</h2>
<div class="grid grid-cols-4 gap-4">
<!-- Status -->
<div class="col-span-1 bg-amber-50 p-4 rounded">
<h3 class="font-bold mb-2">Hero</h3>
<div class="flex items-center mb-1">
<span class="w-16">HP:</span>
<div class="flex-1 health-bar h-4 bg-red-500 rounded">
<div id="hp-bar" class="h-full bg-green-500 rounded" style="width: 100%"></div>
</div>
</div>
<div class="flex items-center mb-1">
<span class="w-16">MP:</span>
<div class="flex-1 health-bar h-4 bg-blue-100 rounded">
<div id="mp-bar" class="h-full bg-blue-500 rounded" style="width: 80%"></div>
</div>
</div>
<div class="mt-2">
<div>Level: <span id="player-level">1</span></div>
<div>Exp: <span id="player-exp">0</span>/100</div>
<div>Gold: <span id="player-gold">50</span></div>
</div>
</div>
<!-- Inventory -->
<div class="col-span-2 bg-amber-50 p-4 rounded">
<h3 class="font-bold mb-2">Inventory</h3>
<div id="inventory-items" class="grid grid-cols-3 gap-2">
<!-- Items will be added here -->
</div>
</div>
<!-- Commands -->
<div class="col-span-1 bg-amber-50 p-4 rounded">
<h3 class="font-bold mb-2">Commands</h3>
<button id="menu-close" class="w-full bg-amber-600 text-white py-2 px-4 rounded mb-2">Close</button>
<button id="menu-quests" class="w-full bg-purple-600 text-white py-2 px-4 rounded mb-2">Quests</button>
<button id="menu-save" class="w-full bg-blue-600 text-white py-2 px-4 rounded mb-2">Save</button>
<button id="menu-load" class="w-full bg-green-600 text-white py-2 px-4 rounded mb-2">Load</button>
<button id="menu-quit" class="w-full bg-red-600 text-white py-2 px-4 rounded">Quit</button>
</div>
</div>
<!-- Quest Log -->
<div id="quest-log" class="hidden mt-4 bg-amber-50 p-4 rounded">
<h3 class="font-bold mb-2 text-center">Quest Log</h3>
<div id="active-quests" class="mb-4">
<h4 class="font-bold mb-1">Active Quests</h4>
<div id="active-quests-list"></div>
</div>
<div id="completed-quests">
<h4 class="font-bold mb-1">Completed Quests</h4>
<div id="completed-quests-list"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Battle Screen -->
<div id="battle-screen" class="absolute top-0 left-0 w-full h-full battle-arena hidden">
<div class="absolute top-4 left-4">
<div class="bg-amber-100 text-gray-900 p-2 rounded">
<div class="font-bold">Hero</div>
<div class="flex items-center mb-1">
<span class="w-8">HP:</span>
<div class="flex-1 health-bar h-3 bg-red-500 rounded">
<div id="battle-hp-bar" class="h-full bg-green-500 rounded" style="width: 100%"></div>
</div>
</div>
<div class="flex items-center">
<span class="w-8">MP:</span>
<div class="flex-1 health-bar h-3 bg-blue-100 rounded">
<div id="battle-mp-bar" class="h-full bg-blue-500 rounded" style="width: 80%"></div>
</div>
</div>
</div>
</div>
<div class="absolute bottom-4 right-4">
<div class="bg-amber-100 text-gray-900 p-2 rounded">
<div class="font-bold">Goblin</div>
<div class="flex items-center mb-1">
<span class="w-8">HP:</span>
<div class="flex-1 health-bar h-3 bg-red-500 rounded">
<div id="enemy-hp-bar" class="h-full bg-green-500 rounded" style="width: 100%"></div>
</div>
</div>
</div>
</div>
<!-- Battle Characters -->
<div id="battle-player" class="absolute bottom-16 left-16 w-16 h-16"
style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iI2ZmMDAwMCIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PHBhdGggZmlsbD0iI2ZmODAwMCIgZD0iTTE2IDhjLTIuMjA5IDAtNCAxLjc5MS00IDRzMS43OTEgNCA0IDQgNC0xLjc5MSA0LTRzLTEuNzkxLTQtNC00eiIvPjwvc3ZnPg=='); background-size: 64px 64px;"></div>
<div id="battle-enemy" class="absolute bottom-16 right-16 w-16 h-16"
style="background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iIzAwODAwMCIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PHBhdGggZmlsbD0iIzAwMDAwMCIgZD0iTTE2IDhjLTIuMjA5IDAtNCAxLjc5MS00IDRzMS43OTEgNCA0IDQgNC0xLjc5MSA0LTRzLTEuNzkxLTQtNC00eiIvPjwvc3ZnPg=='); background-size: 64px 64px;"></div>
<!-- Battle Commands -->
<div id="battle-commands" class="absolute bottom-4 left-4 right-4 bg-amber-100 text-gray-900 p-4 rounded">
<div id="main-commands" class="grid grid-cols-2 gap-2">
<button id="battle-attack" class="bg-red-500 text-white py-2 px-4 rounded">Attack</button>
<button id="battle-magic" class="bg-blue-500 text-white py-2 px-4 rounded">Magic</button>
<button id="battle-item" class="bg-green-500 text-white py-2 px-4 rounded">Item</button>
<button id="battle-flee" class="bg-gray-500 text-white py-2 px-4 rounded">Flee</button>
</div>
<div id="magic-commands" class="grid grid-cols-2 gap-2 hidden">
<button class="magic-option bg-blue-400 text-white py-2 px-4 rounded" data-spell="fire">Fire (10 MP)</button>
<button class="magic-option bg-blue-400 text-white py-2 px-4 rounded" data-spell="heal">Heal (15 MP)</button>
<button class="magic-option bg-blue-400 text-white py-2 px-4 rounded" data-spell="thunder">Thunder (20 MP)</button>
<button id="back-to-main" class="bg-gray-500 text-white py-2 px-4 rounded">Back</button>
</div>
</div>
<!-- Battle Log -->
<div id="battle-log" class="absolute top-16 left-4 right-4 bg-black bg-opacity-70 text-white p-2 text-sm hidden">
<div>Hero attacks Goblin for 10 damage!</div>
</div>
</div>
<!-- Title Screen -->
<div id="title-screen" class="absolute top-0 left-0 w-full h-full bg-gray-900 flex flex-col items-center justify-center">
<h1 class="text-4xl font-bold mb-8 text-yellow-400">RPG Adventure</h1>
<div class="flex flex-col space-y-4 w-64">
<button id="start-game" class="bg-yellow-600 hover:bg-yellow-700 text-white py-2 px-4 rounded">New Game</button>
<button id="load-game" class="bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded">Load Game</button>
<button id="quit-game" class="bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded">Quit</button>
</div>
</div>
<!-- HUD -->
<div class="absolute top-4 left-4 bg-black bg-opacity-50 text-white p-2 rounded">
<div>HP: <span id="hud-hp">100</span>/100</div>
<div>MP: <span id="hud-mp">50</span>/50</div>
<div>Lv: <span id="hud-level">1</span></div>
</div>
<div class="absolute top-4 right-4 bg-black bg-opacity-50 text-white p-2 rounded">
<button id="open-menu" class="bg-gray-700 hover:bg-gray-600 text-white py-1 px-2 rounded">
<i class="fas fa-bars"></i> Menu
</button>
</div>
</div>
<script>
// Game State
const gameState = {
player: {
x: 100,
y: 100,
direction: 'down',
moving: false,
hp: 100,
maxHp: 100,
mp: 50,
maxMp: 50,
level: 1,
exp: 0,
expToNext: 100,
gold: 50,
inventory: [
{ id: 'potion', name: 'Potion', quantity: 3, type: 'heal', power: 30 },
{ id: 'ether', name: 'Ether', quantity: 1, type: 'mana', power: 20 },
{ id: 'sword', name: 'Iron Sword', quantity: 1, type: 'weapon', power: 10 }
],
equipped: {
weapon: 'sword'
}
},
npcs: [
{ id: 'npc1', x: 200, y: 150, name: 'Villager', dialog: ['Hello traveler!', 'Welcome to our village.', 'The mayor is looking for help with a rat problem in the cellar.'] },
{ id: 'npc2', x: 300, y: 200, name: 'Shopkeeper', dialog: ['I sell potions and weapons.', 'Come back when you have more gold!'] },
{ id: 'npc3', x: 150, y: 250, name: 'Mayor', dialog: ['Please help us!', 'Giant rats invaded our cellar!', 'Clear them out and I\'ll reward you.'], quest: 'rat_quest' },
{ id: 'npc4', x: 350, y: 100, name: 'Blacksmith', dialog: ['I can forge weapons if you bring me materials.'] },
{ id: 'npc5', x: 400, y: 300, name: 'Mage', dialog: ['The ancient ruins to the east hold powerful magic.', 'But beware of the guardians!'] }
],
quests: {
rat_quest: {
name: 'Rat Extermination',
description: 'Clear 5 giant rats from the village cellar',
target: { type: 'kill', enemy: 'giant_rat', count: 5 },
reward: { gold: 100, exp: 50, items: [{ id: 'potion', quantity: 3 }] },
completed: false
}
},
activeQuests: [],
items: [
{ id: 'chest', x: 250, y: 100, item: { id: 'potion', name: 'Potion', quantity: 2, type: 'heal', power: 30 }, opened: false }
],
inDialog: false,
dialogIndex: 0,
currentDialog: [],
currentNpc: null,
inBattle: false,
battleTurn: 'player',
battleEnemies: [
{
id: 'goblin',
name: 'Goblin',
hp: 50,
maxHp: 50,
attack: 8,
defense: 2,
speed: 5,
exp: 25,
gold: 10,
sprite: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iIzAwODAwMCIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PHBhdGggZmlsbD0iIzAwMDAwMCIgZD0iTTE2IDhjLTIuMjA5IDAtNCAxLjc5MS00IDRzMS43OTEgNCA0IDQgNC0xLjc5MSA0LTRzLTEuNzkxLTQtNC00eiIvPjwvc3ZnPg=='
},
{
id: 'giant_rat',
name: 'Giant Rat',
hp: 30,
maxHp: 30,
attack: 5,
defense: 1,
speed: 7,
exp: 15,
gold: 5,
sprite: 'data:image/svg+xml;base64,...'
}
],
currentEnemies: [],
gameStarted: false,
currentMap: 'village',
maps: {
village: {
name: 'Village',
width: 30,
height: 20,
tiles: [], // Generated in code
npcs: ['npc1', 'npc2', 'npc3', 'npc4'],
exits: [
{ x: 0, y: 10, to: 'forest', toX: 28, toY: 10 },
{ x: 15, y: 0, to: 'cellar', toX: 5, toY: 5 }
]
},
forest: {
name: 'Forest',
width: 30,
height: 20,
tiles: [],
npcs: ['npc5'],
exits: [
{ x: 29, y: 10, to: 'village', toX: 1, toY: 10 }
]
},
cellar: {
name: 'Cellar',
width: 15,
height: 15,
tiles: [],
exits: [
{ x: 5, y: 0, to: 'village', toX: 15, toY: 1 }
]
}
}
};
// DOM Elements
const player = document.getElementById('player');
const npc1 = document.getElementById('npc1');
const npc2 = document.getElementById('npc2');
const chest = document.getElementById('chest');
const dialogBox = document.getElementById('dialog-box');
const dialogText = document.getElementById('dialog-text');
const dialogName = document.getElementById('dialog-name');
const dialogPortrait = document.getElementById('dialog-portrait');
const dialogNext = document.getElementById('dialog-next');
const menu = document.getElementById('menu');
const openMenu = document.getElementById('open-menu');
const menuClose = document.getElementById('menu-close');
const hpBar = document.getElementById('hp-bar');
const mpBar = document.getElementById('mp-bar');
const playerLevel = document.getElementById('player-level');
const playerExp = document.getElementById('player-exp');
const playerGold = document.getElementById('player-gold');
const inventoryItems = document.getElementById('inventory-items');
const battleScreen = document.getElementById('battle-screen');
const battlePlayer = document.getElementById('battle-player');
const battleEnemy = document.getElementById('battle-enemy');
const battleCommands = document.getElementById('battle-commands');
const battleLog = document.getElementById('battle-log');
const battleAttack = document.getElementById('battle-attack');
const battleMagic = document.getElementById('battle-magic');
const battleItem = document.getElementById('battle-item');
const battleFlee = document.getElementById('battle-flee');
const battleHpBar = document.getElementById('battle-hp-bar');
const battleMpBar = document.getElementById('battle-mp-bar');
const enemyHpBar = document.getElementById('enemy-hp-bar');
const hudHp = document.getElementById('hud-hp');
const hudMp = document.getElementById('hud-mp');
const hudLevel = document.getElementById('hud-level');
const titleScreen = document.getElementById('title-screen');
const startGame = document.getElementById('start-game');
const loadGame = document.getElementById('load-game');
const quitGame = document.getElementById('quit-game');
// Initialize game
function initGame() {
// Position elements
updatePlayerPosition();
updateNpcPositions();
updateItemPositions();
// Update HUD
updateHUD();
// Set up event listeners
setupEventListeners();
// Generate map tiles
generateMapTiles();
// Show title screen
titleScreen.classList.remove('hidden');
}
// Generate simple map tiles
function generateMapTiles() {
const mapTiles = document.getElementById('map-tiles');
mapTiles.innerHTML = '';
// Create grass tiles
for (let y = 0; y < 20; y++) {
for (let x = 0; x < 30; x++) {
const tile = document.createElement('div');
tile.className = 'absolute w-8 h-8';
// Random grass variation
const grassTypes = ['bg-green-600', 'bg-green-700', 'bg-green-800'];
const randomGrass = grassTypes[Math.floor(Math.random() * grassTypes.length)];
tile.classList.add(randomGrass);
// Add some decorative elements
if (Math.random() < 0.02) {
tile.style.backgroundImage = "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iIzAwODAwMCIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PC9zdmc+')";
tile.style.backgroundSize = '32px 32px';
} else if (Math.random() < 0.03) {
tile.style.backgroundImage = "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PHBhdGggZmlsbD0iIzAwODAwMCIgZD0iTTE2IDRjLTYuNjI3IDAtMTIgNS4zNzMtMTIgMTJzNS4zNzMgMTIgMTIgMTIgMTItNS4zNzMgMTItMTItNS4zNzMtMTItMTItMTJ6Ii8+PC9zdmc+')";
tile.style.backgroundSize = '32px 32px';
tile.style.transform = 'scale(0.5)';
}
tile.style.left = `${x * 32}px`;
tile.style.top = `${y * 32}px`;
mapTiles.appendChild(tile);
}
}
// Add some paths
for (let x = 10; x < 20; x++) {
for (let y = 8; y < 12; y++) {
const tile = document.createElement('div');
tile.className = 'absolute w-8 h-8 bg-yellow-800';
tile.style.left = `${x * 32}px`;
tile.style.top = `${y * 32}px`;
mapTiles.appendChild(tile);
}
}
// Add a house
for (let x = 5; x < 10; x++) {
for (let y = 5; y < 10; y++) {
const tile = document.createElement('div');
tile.className = 'absolute w-8 h-8 bg-red-800';
if (x === 7 && y === 9) {
// Door
tile.className = 'absolute w-8 h-8 bg-yellow-600';
}
tile.style.left = `${x * 32}px`;
tile.style.top = `${y * 32}px`;
mapTiles.appendChild(tile);
}
}
}
// Update player position
function updatePlayerPosition() {
player.style.left = `${gameState.player.x}px`;
player.style.top = `${gameState.player.y}px`;
// Update sprite direction
player.style.backgroundPositionY = {
'up': '-32px',
'down': '0px',
'left': '-64px',
'right': '-96px'
}[gameState.player.direction];
// Update moving animation
if (gameState.player.moving) {
player.classList.add('moving');
} else {
player.classList.remove('moving');
}
}
// Update NPC positions
function updateNpcPositions() {
gameState.npcs.forEach(npc => {
const npcElement = document.getElementById(npc.id);
if (npcElement) {
npcElement.style.left = `${npc.x}px`;
npcElement.style.top = `${npc.y}px`;
}
});
}
// Update item positions
function updateItemPositions() {
gameState.items.forEach(item => {
const itemElement = document.getElementById(item.id);
if (itemElement) {
itemElement.style.left = `${item.x}px`;
itemElement.style.top = `${item.y}px`;
itemElement.style.display = item.opened ? 'none' : 'block';
}
});
}
// Update HUD
function updateHUD() {
hudHp.textContent = gameState.player.hp;
hudMp.textContent = gameState.player.mp;
hudLevel.textContent = gameState.player.level;
// Update menu stats
hpBar.style.width = `${(gameState.player.hp / gameState.player.maxHp) * 100}%`;
mpBar.style.width = `${(gameState.player.mp / gameState.player.maxMp) * 100}%`;
playerLevel.textContent = gameState.player.level;
playerExp.textContent = `${gameState.player.exp}/${gameState.player.expToNext}`;
playerGold.textContent = gameState.player.gold;
// Update inventory
updateInventory();
}
// Update inventory display
function updateInventory() {
inventoryItems.innerHTML = '';
gameState.player.inventory.forEach(item => {
const itemElement = document.createElement('div');
itemElement.className = 'bg-amber-100 p-2 rounded text-center';
const itemName = document.createElement('div');
itemName.className = 'font-bold';
itemName.textContent = item.name;
const itemQuantity = document.createElement('div');
itemQuantity.className = 'text-sm';
itemQuantity.textContent = `x${item.quantity}`;
itemElement.appendChild(itemName);
itemElement.appendChild(itemQuantity);
// Add click event to use item
itemElement.addEventListener('click', () => {
if (item.type === 'heal') {
gameState.player.hp = Math.min(gameState.player.hp + item.power, gameState.player.maxHp);
item.quantity--;
if (item.quantity <= 0) {
gameState.player.inventory = gameState.player.inventory.filter(i => i.id !== item.id);
}
updateHUD();
showBattleMessage(`Used ${item.name}! Restored ${item.power} HP.`);
} else if (item.type === 'mana') {
gameState.player.mp = Math.min(gameState.player.mp + item.power, gameState.player.maxMp);
item.quantity--;
if (item.quantity <= 0) {
gameState.player.inventory = gameState.player.inventory.filter(i => i.id !== item.id);
}
updateHUD();
showBattleMessage(`Used ${item.name}! Restored ${item.power} MP.`);
}
});
inventoryItems.appendChild(itemElement);
});
}
// Show dialog
function showDialog(npc) {
gameState.inDialog = true;
gameState.currentNpc = npc;
gameState.currentDialog = npc.dialog;
gameState.dialogIndex = 0;
dialogName.textContent = npc.name;
dialogText.textContent = npc.dialog[0];
// Set portrait based on NPC
dialogPortrait.style.backgroundImage = document.getElementById(npc.id).style.backgroundImage;
dialogBox.classList.remove('hidden');
}
// Next dialog
function nextDialog() {
gameState.dialogIndex++;
if (gameState.dialogIndex < gameState.currentDialog.length) {
dialogText.textContent = gameState.currentDialog[gameState.dialogIndex];
} else {
// End dialog
dialogBox.classList.add('hidden');
gameState.inDialog = false;
gameState.currentDialog = [];
gameState.dialogIndex = 0;
}
}
// Check for interactions
function checkInteractions() {
// Check if player is near an NPC
for (const npc of gameState.npcs) {
const distance = Math.sqrt(
Math.pow(gameState.player.x - npc.x, 2) +
Math.pow(gameState.player.y - npc.y, 2)
);
if (distance < 40 && !gameState.inDialog) {
showDialog(npc);
return;
}
}
// Check if player is near an item
for (const item of gameState.items) {
if (item.opened) continue;
const distance = Math.sqrt(
Math.pow(gameState.player.x - item.x, 2) +
Math.pow(gameState.player.y - item.y, 2)
);
if (distance < 40) {
// Open chest
item.opened = true;
// Add item to inventory
const existingItem = gameState.player.inventory.find(i => i.id === item.item.id);
if (existingItem) {
existingItem.quantity += item.item.quantity;
} else {
gameState.player.inventory.push({...item.item});
}
updateItemPositions();
updateHUD();
// Show message
showDialog({
name: 'Treasure Chest',
dialog: [`You found ${item.item.quantity}x ${item.item.name}!`]
});
return;
}
}
// Random battle chance when moving
if (gameState.player.moving && Math.random() < 0.005 && !gameState.inBattle && !gameState.inDialog) {
startBattle();
}
}
// Start battle
function startBattle() {
gameState.inBattle = true;
battleScreen.classList.remove('hidden');
// Reset enemy HP
gameState.battleEnemy.hp = gameState.battleEnemy.maxHp;
// Update battle UI
battleHpBar.style.width = '100%';
battleMpBar.style.width = `${(gameState.player.mp / gameState.player.maxMp) * 100}%`;
enemyHpBar.style.width = '100%';
// Show battle message
showBattleMessage(`A wild ${gameState.battleEnemy.name} appears!`);
}
// End battle
function endBattle(victory) {
gameState.inBattle = false;
battleScreen.classList.add('hidden');
if (victory) {
// Give rewards
gameState.player.exp += gameState.battleEnemy.exp;
gameState.player.gold += gameState.battleEnemy.gold;
// Check for level up
checkLevelUp();
// Show message
showDialog({
name: 'Battle',
dialog: [
`You defeated the ${gameState.battleEnemy.name}!`,
`Gained ${gameState.battleEnemy.exp} EXP and ${gameState.battleEnemy.gold} Gold.`
]
});
} else {
// Game over
gameState.player.hp = Math.floor(gameState.player.maxHp / 2);
showDialog({
name: 'Battle',
dialog: [
`You were defeated by the ${gameState.battleEnemy.name}!`,
'You wake up at the village with half HP.'
]
});
}
updateHUD();
}
// Check for level up
function checkLevelUp() {
if (gameState.player.exp >= gameState.player.expToNext) {
gameState.player.level++;
gameState.player.exp -= gameState.player.expToNext;
gameState.player.expToNext = Math.floor(gameState.player.expToNext * 1.5);
// Increase stats
gameState.player.maxHp += 10;
gameState.player.hp = gameState.player.maxHp;
gameState.player.maxMp += 5;
gameState.player.mp = gameState.player.maxMp;
// Show message
showDialog({
name: 'Level Up',
dialog: [
`Congratulations! You reached Level ${gameState.player.level}!`,
'Your stats have increased!'
]
});
// Check for multiple level ups
checkLevelUp();
}
}
// Player attack in battle
function playerAttack() {
const damage = Math.floor(Math.random() * 10) + 10; // 10-20 damage
gameState.battleEnemy.hp = Math.max(0, gameState.battleEnemy.hp - damage);
// Update enemy HP bar
enemyHpBar.style.width = `${(gameState.battleEnemy.hp / gameState.battleEnemy.maxHp) * 100}%`;
// Show message
showBattleMessage(`You attack for ${damage} damage!`);
// Check if enemy is defeated
if (gameState.battleEnemy.hp <= 0) {
setTimeout(() => {
endBattle(true);
}, 1000);
return;
}
// Enemy attack
setTimeout(() => {
enemyAttack();
}, 1000);
}
// Enemy attack
function enemyAttack() {
const damage = Math.floor(Math.random() * gameState.battleEnemy.attack) + 1;
gameState.player.hp = Math.max(0, gameState.player.hp - damage);
// Update player HP bar
battleHpBar.style.width = `${(gameState.player.hp / gameState.player.maxHp) * 100}%`;
hudHp.textContent = gameState.player.hp;
// Show message
showBattleMessage(`${gameState.battleEnemy.name} attacks for ${damage} damage!`);
// Check if player is defeated
if (gameState.player.hp <= 0) {
setTimeout(() => {
endBattle(false);
}, 1000);
}
}
// Show battle message
function showBattleMessage(message) {
battleLog.innerHTML = message;
battleLog.classList.remove('hidden');
setTimeout(() => {
battleLog.classList.add('hidden');
}, 2000);
}
// Set up event listeners
function setupEventListeners() {
// Keyboard controls
document.addEventListener('keydown', (e) => {
if (gameState.inDialog || gameState.inBattle || !gameState.gameStarted) return;
const speed = 2;
let moved = false;
switch (e.key) {
case 'ArrowUp':
gameState.player.y -= speed;
gameState.player.direction = 'up';
moved = true;
break;
case 'ArrowDown':
gameState.player.y += speed;
gameState.player.direction = 'down';
moved = true;
break;
case 'ArrowLeft':
gameState.player.x -= speed;
gameState.player.direction = 'left';
moved = true;
break;
case 'ArrowRight':
gameState.player.x += speed;
gameState.player.direction = 'right';
moved = true;
break;
case ' ':
// Space to interact
checkInteractions();
break;
case 'Escape':
// Open menu
menu.classList.remove('hidden');
break;
}
if (moved) {
gameState.player.moving = true;
updatePlayerPosition();
// Check for interactions when stopping
setTimeout(() => {
if (!gameState.player.moving) {
checkInteractions();
}
}, 100);
}
});
document.addEventListener('keyup', (e) => {
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
gameState.player.moving = false;
updatePlayerPosition();
}
});
// Dialog next button
dialogNext.addEventListener('click', nextDialog);
// Menu buttons
openMenu.addEventListener('click', () => {
menu.classList.remove('hidden');
});
menuClose.addEventListener('click', () => {
menu.classList.add('hidden');
});
// Battle buttons
battleAttack.addEventListener('click', playerAttack);
battleMagic.addEventListener('click', () => {
if (gameState.player.mp >= 10) {
gameState.player.mp -= 10;
const damage = Math.floor(Math.random() * 15) + 15; // 15-30 damage
gameState.battleEnemy.hp = Math.max(0, gameState.battleEnemy.hp - damage);
// Update UI
battleMpBar.style.width = `${(gameState.player.mp / gameState.player.maxMp) * 100}%`;
enemyHpBar.style.width = `${(gameState.battleEnemy.hp / gameState.battleEnemy.maxHp) * 100}%`;
// Show message
showBattleMessage(`You cast Fire for ${damage} damage!`);
// Check if enemy is defeated
if (gameState.battleEnemy.hp <= 0) {
setTimeout(() => {
endBattle(true);
}, 1000);
return;
}
// Enemy attack
setTimeout(() => {
enemyAttack();
}, 1000);
} else {
showBattleMessage('Not enough MP!');
}
});
battleItem.addEventListener('click', () => {
// In a full game, this would open an item menu
showBattleMessage('Select an item from the menu.');
});
battleFlee.addEventListener('click', () => {
if (Math.random() < 0.7) { // 70% chance to flee
showBattleMessage('You fled successfully!');
setTimeout(() => {
battleScreen.classList.add('hidden');
gameState.inBattle = false;
}, 1000);
} else {
showBattleMessage('Failed to flee!');
setTimeout(() => {
enemyAttack();
}, 1000);
}
});
// Title screen buttons
startGame.addEventListener('click', () => {
titleScreen.classList.add('hidden');
gameState.gameStarted = true;
});
loadGame.addEventListener('click', () => {
// In a full game, this would load saved data
showDialog({
name: 'System',
dialog: ['No saved game found.']
});
titleScreen.classList.add('hidden');
gameState.gameStarted = true;
});
quitGame.addEventListener('click', () => {
if (confirm('Are you sure you want to quit?')) {
window.close();
}
});
}
// Start the game
initGame();
</script>
<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=sonusahani/ok" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>