tiny-thief-remake / index.html
Hosdroid's picture
Create index.html
ad8da71 verified
Raw
History Blame Contribute Delete
55.9 kB
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-select=none">
<title>دزد کوچک - شاهکار هفت مرحله‌ای</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: radial-gradient(ellipse at center, #1a1a2e 0%, #0a0a15 100%);
font-family: 'Vazirmatn', 'Tahoma', sans-serif;
user-select: none;
-webkit-user-select: none;
touch-action: none;
}
#game-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
#game-wrapper {
position: relative;
box-shadow: 0 0 80px rgba(100, 150, 255, 0.3), 0 20px 60px rgba(0,0,0,0.9);
border: 3px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
overflow: hidden;
backdrop-filter: blur(10px);
}
canvas {
display: block;
background-color: #87ceeb;
cursor: pointer;
}
#ui-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 15px;
}
#level-info {
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(12px);
color: #fff;
padding: 10px 20px;
border-radius: 25px;
font-size: 16px;
font-weight: bold;
align-self: center;
border: 1px solid rgba(255, 255, 255, 0.2);
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
display: flex;
gap: 20px;
align-items: center;
}
#level-info .stars {
color: #f1c40f;
letter-spacing: 3px;
}
#touch-hint {
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(12px);
color: #fff;
padding: 10px 20px;
border-radius: 25px;
font-size: 13px;
align-self: center;
border: 1px solid rgba(255, 255, 255, 0.2);
text-align: center;
opacity: 0.9;
transition: opacity 0.5s;
max-width: 90%;
}
#modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(8px);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
pointer-events: auto;
z-index: 10;
}
#modal-overlay.active {
display: flex;
}
#modal-title {
font-size: 42px;
font-weight: 900;
margin-bottom: 20px;
text-shadow: 0 4px 12px rgba(0,0,0,0.8);
text-align: center;
}
#modal-subtitle {
font-size: 18px;
color: #ddd;
margin-bottom: 30px;
text-align: center;
max-width: 80%;
line-height: 1.6;
}
#modal-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 14px 40px;
font-size: 18px;
font-family: 'Vazirmatn', sans-serif;
font-weight: bold;
border-radius: 30px;
cursor: pointer;
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5);
transition: transform 0.2s, box-shadow 0.2s;
pointer-events: auto;
}
#modal-btn:hover {
transform: translateY(-2px);
box-shadow: 0 12px 30px rgba(102, 126, 234, 0.7);
}
#modal-btn:active {
transform: translateY(0);
}
.caught-title { color: #e74c3c; }
.win-title { color: #2ecc71; }
.complete-title { color: #f1c40f; }
@media (max-width: 768px) {
#level-info { font-size: 13px; padding: 8px 14px; }
#touch-hint { font-size: 11px; padding: 8px 14px; }
#modal-title { font-size: 30px; }
#modal-subtitle { font-size: 15px; }
}
</style>
</head>
<body>
<div id="game-container">
<div id="game-wrapper">
<canvas id="gameCanvas" width="1024" height="576"></canvas>
<div id="ui-overlay">
<div id="level-info">
<span id="level-name">مرحله ۱: حیاط قلعه</span>
<span class="stars" id="level-stars">⭐⭐⭐</span>
</div>
<div id="touch-hint">روی زمین ضربه بزنید تا حرکت کنید. روی وسایل ضربه بزنید تا تعامل کنید!</div>
</div>
<div id="modal-overlay">
<div id="modal-title"></div>
<div id="modal-subtitle"></div>
<button id="modal-btn">ادامه</button>
</div>
</div>
</div>
<script>
(function() {
'use strict';
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const CANVAS_W = 1024;
const CANVAS_H = 576;
// --- Background Image ---
const bgImage = new Image();
bgImage.src = 'https://image.qwenlm.ai/public_source/0c0c257b-8e6f-4765-9570-24c71df5f2e4/1c6f7c13d-1306-4149-9603-f57815a260ce.png';
let bgImageLoaded = false;
bgImage.onload = () => { bgImageLoaded = true; };
// --- Responsive Scaling ---
function resizeCanvas() {
const aspect = CANVAS_W / CANVAS_H;
let w = window.innerWidth;
let h = window.innerHeight;
if (w / h > aspect) {
w = h * aspect;
} else {
h = w / aspect;
}
canvas.style.width = w + 'px';
canvas.style.height = h + 'px';
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// --- Audio System ---
let audioCtx = null;
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') audioCtx.resume();
}
function playSound(sndType) {
if (!audioCtx) return;
try {
const now = audioCtx.currentTime;
if (sndType === 'step') {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain); gain.connect(audioCtx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(180, now);
osc.frequency.exponentialRampToValueAtTime(280, now + 0.08);
gain.gain.setValueAtTime(0.08, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.1);
osc.start(now); osc.stop(now + 0.12);
} else if (sndType === 'alert') {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain); gain.connect(audioCtx.destination);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(350, now);
osc.frequency.setValueAtTime(700, now + 0.1);
gain.gain.setValueAtTime(0.15, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3);
osc.start(now); osc.stop(now + 0.35);
} else if (sndType === 'steal') {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain); gain.connect(audioCtx.destination);
osc.type = 'triangle';
osc.frequency.setValueAtTime(523, now);
osc.frequency.setValueAtTime(659, now + 0.1);
osc.frequency.setValueAtTime(784, now + 0.2);
gain.gain.setValueAtTime(0.12, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
osc.start(now); osc.stop(now + 0.45);
} else if (sndType === 'gameover') {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain); gain.connect(audioCtx.destination);
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, now);
osc.frequency.exponentialRampToValueAtTime(60, now + 0.6);
gain.gain.setValueAtTime(0.25, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.7);
osc.start(now); osc.stop(now + 0.75);
} else if (sndType === 'win') {
const notes = [523, 587, 659, 698, 784, 880, 988, 1047];
notes.forEach((freq, idx) => {
const o = audioCtx.createOscillator();
const g = audioCtx.createGain();
o.connect(g); g.connect(audioCtx.destination);
o.type = 'sine';
o.frequency.setValueAtTime(freq, now + idx * 0.07);
g.gain.setValueAtTime(0.1, now + idx * 0.07);
g.gain.exponentialRampToValueAtTime(0.001, now + idx * 0.07 + 0.12);
o.start(now + idx * 0.07);
o.stop(now + idx * 0.07 + 0.15);
});
} else if (sndType === 'hide') {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain); gain.connect(audioCtx.destination);
osc.type = 'sine';
osc.frequency.setValueAtTime(400, now);
osc.frequency.exponentialRampToValueAtTime(200, now + 0.15);
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
osc.start(now); osc.stop(now + 0.25);
} else if (sndType === 'break') {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain); gain.connect(audioCtx.destination);
osc.type = 'square';
osc.frequency.setValueAtTime(100, now);
osc.frequency.exponentialRampToValueAtTime(50, now + 0.3);
gain.gain.setValueAtTime(0.2, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
osc.start(now); osc.stop(now + 0.45);
}
} catch (e) {}
}
// --- Game State ---
let gameState = 'playing';
let currentLevelIdx = 0;
let totalStars = 0;
let particles = [];
let floatingTexts = [];
let lastTime = 0;
// --- Level Definitions ---
const levelDefs = [
{
name: 'حیاط قلعه',
hint: 'بشکه برای مخفی شدن، صندوقچه برای کلید',
bgType: 'image',
groundColor: '#81c784',
groundY: 435,
thiefStart: { x: 100, y: 355 },
guards: [{ startX: 600, endX: 900, y: 325, speed: 1.8, sightRange: 200, dir: -1 }],
hides: [{ x: 400, y: 345, w: 65, h: 90, type: 'barrel' }],
chest: { x: 650, y: 375, w: 65, h: 60 },
key: { x: 675, y: 310 },
door: { x: 920, y: 305, w: 70, h: 130 }
},
{
name: 'آشپزخانه',
hint: 'مراقب سگ خوابیده باش! کلید روی قفسه است',
bgType: 'kitchen',
groundColor: '#d4a574',
groundY: 435,
thiefStart: { x: 100, y: 355 },
guards: [{ startX: 500, endX: 800, y: 325, speed: 2.0, sightRange: 220, dir: -1 }],
hides: [{ x: 250, y: 345, w: 70, h: 90, type: 'cart' }],
chest: { x: 700, y: 375, w: 65, h: 60 },
key: { x: 720, y: 260 },
door: { x: 920, y: 305, w: 70, h: 130 },
dog: { x: 450, y: 390, awake: false, wakeDist: 100 }
},
{
name: 'کتابخانه',
hint: 'نگهبان چراغ دارد! دید او بیشتر است. پشت قفسه کتاب مخفی شو',
bgType: 'library',
groundColor: '#5d4037',
groundY: 435,
thiefStart: { x: 100, y: 355 },
guards: [{ startX: 400, endX: 750, y: 325, speed: 1.5, sightRange: 280, dir: 1, lantern: true }],
hides: [{ x: 280, y: 330, w: 80, h: 105, type: 'bookshelf' }],
chest: { x: 600, y: 375, w: 65, h: 60 },
key: { x: 620, y: 310 },
door: { x: 900, y: 305, w: 70, h: 130, secret: true }
},
{
name: 'زندان',
hint: 'دو نگهبان! وقتی هر دو پشت کردند حرکت کن',
bgType: 'dungeon',
groundColor: '#424242',
groundY: 435,
thiefStart: { x: 100, y: 355 },
guards: [
{ startX: 300, endX: 550, y: 325, speed: 1.6, sightRange: 180, dir: 1 },
{ startX: 600, endX: 850, y: 325, speed: 1.4, sightRange: 180, dir: -1 }
],
hides: [{ x: 540, y: 340, w: 50, h: 95, type: 'chain' }],
chest: { x: 750, y: 375, w: 65, h: 60 },
key: { x: 770, y: 310 },
door: { x: 920, y: 340, w: 70, h: 95, grate: true }
},
{
name: 'تالار تخت پادشاه',
hint: 'نگهبان سلطنتی! پشت تخت مخفی شو و کلید را بردار',
bgType: 'throne',
groundColor: '#8e24aa',
groundY: 435,
thiefStart: { x: 100, y: 355 },
guards: [{ startX: 500, endX: 850, y: 325, speed: 2.2, sightRange: 240, dir: -1, royal: true }],
hides: [{ x: 380, y: 330, w: 90, h: 105, type: 'throne' }],
chest: { x: 420, y: 375, w: 65, h: 60 },
key: { x: 440, y: 300 },
door: { x: 920, y: 305, w: 70, h: 130 }
},
{
name: 'پشت بام',
hint: 'مسیر باریک! دودکش برای مخفی شدن، کلید در لانه پرنده',
bgType: 'rooftop',
groundColor: '#78909c',
groundY: 435,
thiefStart: { x: 100, y: 355 },
guards: [{ startX: 600, endX: 900, y: 325, speed: 2.0, sightRange: 200, dir: -1 }],
hides: [{ x: 280, y: 330, w: 70, h: 105, type: 'chimney' }],
chest: { x: 700, y: 375, w: 65, h: 60 },
key: { x: 720, y: 280 },
door: { x: 940, y: 330, w: 60, h: 105, chimney: true }
},
{
name: 'خزانه گنج',
hint: 'پرتوهای جادویی! شیشه را بشکن (صدا می‌دهد!) و فرار کن',
bgType: 'vault',
groundColor: '#263238',
groundY: 435,
thiefStart: { x: 100, y: 355 },
guards: [{ startX: 700, endX: 920, y: 325, speed: 2.5, sightRange: 260, dir: -1, elite: true }],
hides: [{ x: 350, y: 340, w: 60, h: 95, type: 'pillar' }],
chest: { x: 500, y: 375, w: 65, h: 60, glass: true },
key: { x: 520, y: 310 },
door: { x: 940, y: 305, w: 70, h: 130, vault: true },
beams: [{ x: 200, y: 300, h: 135 }, { x: 400, y: 300, h: 135 }, { x: 600, y: 300, h: 135 }]
}
];
// --- Game Objects ---
let thiefObj = { x: 100, y: 355, w: 50, h: 80, speed: 4.5, targetX: 100, isMoving: false, isHidden: false, dir: 1, animT: 0, scaleY: 1, hasKey: false, completed: false };
let guardObjs = [];
let hideObjs = [];
let chestObj = null;
let keyObj = null;
let doorObj = null;
let dogObj = null;
let beamObjs = [];
let currentLevel = null;
let clouds = [];
function initLevel(idx) {
currentLevelIdx = idx;
currentLevel = levelDefs[idx];
thiefObj.x = currentLevel.thiefStart.x;
thiefObj.y = currentLevel.thiefStart.y;
thiefObj.targetX = currentLevel.thiefStart.x;
thiefObj.isMoving = false;
thiefObj.isHidden = false;
thiefObj.hasKey = false;
thiefObj.completed = false;
thiefObj.dir = 1;
guardObjs = currentLevel.guards.map(g => ({
x: (g.startX + g.endX) / 2,
y: g.y,
w: 65, h: 110,
speed: g.speed,
startX: g.startX,
endX: g.endX,
dir: g.dir,
animT: 0, scaleY: 1,
state: 'patrol',
sightRange: g.sightRange,
lantern: g.lantern || false,
royal: g.royal || false,
elite: g.elite || false,
alertT: 0,
searchX: 0
}));
hideObjs = currentLevel.hides.map(h => ({ ...h, active: true }));
chestObj = {
x: currentLevel.chest.x, y: currentLevel.chest.y,
w: currentLevel.chest.w, h: currentLevel.chest.h,
isOpen: false, glass: currentLevel.chest.glass || false
};
keyObj = {
x: currentLevel.key.x, y: currentLevel.key.y,
w: 25, h: 25, collected: false, visible: false
};
doorObj = {
x: currentLevel.door.x, y: currentLevel.door.y,
w: currentLevel.door.w, h: currentLevel.door.h,
secret: currentLevel.door.secret || false,
grate: currentLevel.door.grate || false,
chimney: currentLevel.door.chimney || false,
vault: currentLevel.door.vault || false
};
dogObj = currentLevel.dog ? { ...currentLevel.dog, awake: false } : null;
beamObjs = currentLevel.beams ? currentLevel.beams.map(b => ({ ...b, phase: Math.random() * Math.PI * 2 })) : [];
particles = [];
floatingTexts = [];
gameState = 'playing';
document.getElementById('level-name').textContent = `مرحله ${idx + 1}: ${currentLevel.name}`;
document.getElementById('touch-hint').textContent = currentLevel.hint;
updateStars();
hideModal();
}
function updateStars() {
const earned = Math.min(3, Math.floor(totalStars / 7) + (currentLevelIdx < totalStars % 7 || currentLevelIdx === 0 && totalStars > 0 ? 1 : 0));
let s = '';
for (let i = 0; i < 3; i++) s += i < Math.min(3, Math.floor(totalStars / 7) + 1) ? '⭐' : '☆';
document.getElementById('level-stars').textContent = s;
}
// --- Particles ---
function spawnParticles(px, py, color, count = 12) {
for (let i = 0; i < count; i++) {
particles.push({
x: px, y: py,
vx: (Math.random() - 0.5) * 8,
vy: (Math.random() - 0.5) * 8 - 4,
radius: Math.random() * 5 + 2,
color: color,
alpha: 1,
life: 1
});
}
}
function spawnFloatingText(px, py, text, color = '#fff') {
floatingTexts.push({ x: px, y: py, text, color, alpha: 1, vy: -2 });
}
// --- Input Handling ---
function getCanvasCoords(clientX, clientY) {
const rect = canvas.getBoundingClientRect();
const scaleX = CANVAS_W / rect.width;
const scaleY = CANVAS_H / rect.height;
return {
x: (clientX - rect.left) * scaleX,
y: (clientY - rect.top) * scaleY
};
}
canvas.addEventListener('click', (e) => {
initAudio();
const coords = getCanvasCoords(e.clientX, e.clientY);
handleClick(coords.x, coords.y);
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
initAudio();
const touch = e.touches[0];
const coords = getCanvasCoords(touch.clientX, touch.clientY);
handleClick(coords.x, coords.y);
}, { passive: false });
function handleClick(cx, cy) {
if (gameState !== 'playing') return;
// Check hiding spots
for (const hide of hideObjs) {
if (cx > hide.x && cx < hide.x + hide.w && cy > hide.y && cy < hide.y + hide.h) {
const dist = Math.abs((thiefObj.x + thiefObj.w/2) - (hide.x + hide.w/2));
if (dist < 100) {
thiefObj.isHidden = !thiefObj.isHidden;
thiefObj.isMoving = false;
playSound('hide');
spawnParticles(thiefObj.x + thiefObj.w/2, thiefObj.y + thiefObj.h/2, '#9c27b0', 15);
return;
}
}
}
if (thiefObj.isHidden) {
thiefObj.isHidden = false;
spawnParticles(thiefObj.x + thiefObj.w/2, thiefObj.y + thiefObj.h/2, '#9c27b0', 8);
}
// Check chest
if (chestObj && cx > chestObj.x && cx < chestObj.x + chestObj.w && cy > chestObj.y && cy < chestObj.y + chestObj.h) {
const dist = Math.abs((thiefObj.x + thiefObj.w/2) - (chestObj.x + chestObj.w/2));
if (dist < 100) {
if (!chestObj.isOpen) {
chestObj.isOpen = true;
keyObj.visible = true;
playSound('steal');
spawnParticles(chestObj.x + 30, chestObj.y + 20, '#f1c40f', 20);
if (chestObj.glass) {
playSound('break');
spawnParticles(chestObj.x + 30, chestObj.y + 30, '#e0e0e0', 25);
// Alert guards due to noise
guardObjs.forEach(g => {
if (g.state === 'patrol') {
g.state = 'alert';
g.alertT = 0;
}
});
}
}
return;
}
}
// Check key
if (keyObj.visible && !keyObj.collected) {
const dist = Math.abs((thiefObj.x + thiefObj.w/2) - (keyObj.x + keyObj.w/2));
if (dist < 80 && Math.abs(thiefObj.y - keyObj.y) < 100) {
keyObj.collected = true;
thiefObj.hasKey = true;
playSound('win');
spawnParticles(keyObj.x + 10, keyObj.y + 10, '#f39c12', 25);
spawnFloatingText(keyObj.x, keyObj.y - 20, 'کلید!', '#f1c40f');
return;
}
}
// Move thief
thiefObj.targetX = cx - thiefObj.w / 2;
if (thiefObj.targetX < 30) thiefObj.targetX = 30;
if (thiefObj.targetX > CANVAS_W - 80) thiefObj.targetX = CANVAS_W - 80;
thiefObj.isMoving = true;
thiefObj.dir = thiefObj.targetX > thiefObj.x ? 1 : -1;
}
// --- Update Logic ---
function update(dt) {
// Particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.15;
p.alpha -= 0.025;
if (p.alpha <= 0) particles.splice(i, 1);
}
// Floating texts
for (let i = floatingTexts.length - 1; i >= 0; i--) {
const ft = floatingTexts[i];
ft.y += ft.vy;
ft.alpha -= 0.02;
if (ft.alpha <= 0) floatingTexts.splice(i, 1);
}
if (gameState !== 'playing') return;
// Thief movement (smooth lerp)
thiefObj.animT += 0.12;
if (thiefObj.isMoving && !thiefObj.isHidden) {
const diff = thiefObj.targetX - thiefObj.x;
if (Math.abs(diff) < thiefObj.speed) {
thiefObj.x = thiefObj.targetX;
thiefObj.isMoving = false;
} else {
thiefObj.x += Math.sign(diff) * thiefObj.speed;
}
thiefObj.scaleY = 1 + Math.sin(thiefObj.animT * 3) * 0.06;
if (Math.floor(thiefObj.animT * 2) % 3 === 0) playSound('step');
} else {
thiefObj.scaleY = 1 + Math.sin(thiefObj.animT) * 0.02;
}
// Dog logic
if (dogObj && !dogObj.awake) {
const distToDog = Math.abs((thiefObj.x + thiefObj.w/2) - (dogObj.x + 30));
if (distToDog < dogObj.wakeDist && !thiefObj.isHidden) {
dogObj.awake = true;
playSound('alert');
spawnParticles(dogObj.x + 30, dogObj.y, '#e74c3c', 15);
// Wake up guards too
guardObjs.forEach(g => {
if (g.state === 'patrol') { g.state = 'alert'; g.alertT = 0; }
});
}
}
// Beam logic
beamObjs.forEach(b => {
b.phase += 0.03;
if (!thiefObj.isHidden) {
const thiefCenterX = thiefObj.x + thiefObj.w/2;
if (Math.abs(thiefCenterX - b.x) < 20 && thiefObj.y + thiefObj.h > b.y && thiefObj.y < b.y + b.h) {
// Hit by beam - game over
gameState = 'caught';
playSound('gameover');
showModal('پرتو جادویی!', 'به پرتوهای جادویی برخورد کردی!', 'caught');
}
}
});
// Guard AI
guardObjs.forEach(guard => {
guard.animT += 0.08;
if (guard.state === 'patrol') {
guard.scaleY = 1 + Math.sin(guard.animT * 2) * 0.03;
guard.x += guard.speed * guard.dir;
if (guard.x <= guard.startX) { guard.dir = 1; guard.x = guard.startX; }
else if (guard.x >= guard.endX) { guard.dir = -1; guard.x = guard.endX; }
// Sight detection
if (!thiefObj.isHidden) {
const guardEyeX = guard.x + (guard.dir === 1 ? guard.w : 0);
const thiefCenterX = thiefObj.x + thiefObj.w / 2;
const isFacing = (guard.dir === 1 && thiefCenterX > guardEyeX) || (guard.dir === -1 && thiefCenterX < guardEyeX);
if (isFacing) {
const distance = Math.abs(guardEyeX - thiefCenterX);
if (distance < guard.sightRange) {
guard.state = 'alert';
guard.alertT = 0;
playSound('alert');
spawnParticles(guard.x + guard.w/2, guard.y - 10, '#e74c3c', 15);
}
}
}
} else if (guard.state === 'alert') {
guard.alertT++;
guard.scaleY = 1 + Math.sin(guard.animT * 4) * 0.05;
if (guard.alertT > 40) {
guard.state = 'chase';
}
if (thiefObj.isHidden) {
guard.state = 'search';
guard.searchX = thiefObj.x;
guard.alertT = 0;
}
} else if (guard.state === 'chase') {
guard.scaleY = 1 + Math.sin(guard.animT * 5) * 0.08;
const dirToThief = thiefObj.x < guard.x ? -1 : 1;
guard.dir = dirToThief;
guard.x += guard.speed * 3.5 * dirToThief;
if (Math.abs((guard.x + guard.w/2) - (thiefObj.x + thiefObj.w/2)) < 35) {
gameState = 'caught';
playSound('gameover');
showModal('دستگیر شدی!', 'نگهبان تو را گرفت. دوباره تلاش کن!', 'caught');
}
if (thiefObj.isHidden) {
guard.state = 'search';
guard.searchX = thiefObj.x;
guard.alertT = 0;
}
} else if (guard.state === 'search') {
guard.alertT++;
const dirToSearch = guard.searchX < guard.x ? -1 : 1;
guard.x += guard.speed * 1.5 * dirToSearch;
if (Math.abs(guard.x - guard.searchX) < 20 || guard.alertT > 120) {
guard.state = 'patrol';
guard.alertT = 0;
}
if (!thiefObj.isHidden && Math.abs((guard.x + guard.w/2) - (thiefObj.x + thiefObj.w/2)) < 100) {
guard.state = 'chase';
}
}
});
// Check door/win
if (thiefObj.hasKey && doorObj) {
const distToDoor = Math.abs((thiefObj.x + thiefObj.w/2) - (doorObj.x + doorObj.w/2));
if (distToDoor < 50) {
gameState = 'level_complete';
totalStars++;
playSound('win');
spawnParticles(doorObj.x + doorObj.w/2, doorObj.y + doorObj.h/2, '#2ecc71', 50);
if (currentLevelIdx < levelDefs.length - 1) {
showModal('مرحله کامل شد! 🎉', `عالی بود! آماده مرحله بعدی هستی؟`, 'win', () => {
initLevel(currentLevelIdx + 1);
});
} else {
showModal('تبریک! همه مراحل کامل شد! 🏆', `تو یک دزد حرفه‌ای هستی! ستاره‌های جمع‌آوری شده: ${totalStars}`, 'complete', () => {
totalStars = 0;
initLevel(0);
});
}
}
}
}
// --- Drawing ---
function draw() {
ctx.clearRect(0, 0, CANVAS_W, CANVAS_H);
// Background
drawBackground();
// Ground
drawGround();
// Beams (Level 7)
beamObjs.forEach(b => {
const intensity = (Math.sin(b.phase) + 1) / 2;
ctx.save();
ctx.globalAlpha = 0.3 + intensity * 0.4;
const grad = ctx.createLinearGradient(b.x, b.y, b.x, b.y + b.h);
grad.addColorStop(0, '#e74c3c');
grad.addColorStop(0.5, '#f39c12');
grad.addColorStop(1, '#e74c3c');
ctx.fillStyle = grad;
ctx.fillRect(b.x - 8, b.y, 16, b.h);
ctx.shadowColor = '#e74c3c';
ctx.shadowBlur = 20;
ctx.fillRect(b.x - 4, b.y, 8, b.h);
ctx.restore();
});
// Hiding spots
hideObjs.forEach(hide => drawHideSpot(hide));
// Chest
if (chestObj) drawChest();
// Key
if (keyObj.visible && !keyObj.collected) drawKey();
// Door
if (doorObj) drawDoor();
// Dog
if (dogObj) drawDog();
// Particles
particles.forEach(p => {
ctx.save();
ctx.globalAlpha = p.alpha;
ctx.fillStyle = p.color;
ctx.shadowColor = p.color;
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
});
// Floating texts
floatingTexts.forEach(ft => {
ctx.save();
ctx.globalAlpha = ft.alpha;
ctx.fillStyle = ft.color;
ctx.font = 'bold 24px Vazirmatn';
ctx.textAlign = 'center';
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = 4;
ctx.fillText(ft.text, ft.x, ft.y);
ctx.restore();
});
// Guards
guardObjs.forEach(guard => drawGuard(guard));
// Thief
if (!thiefObj.isHidden) {
drawThief();
} else {
// Eyes peeking from hiding spot
const hide = hideObjs.find(h => Math.abs((thiefObj.x + thiefObj.w/2) - (h.x + h.w/2)) < 100);
if (hide) {
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(hide.x + 20, hide.y + 30, 5, 0, Math.PI * 2);
ctx.arc(hide.x + 40, hide.y + 30, 5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(hide.x + 20, hide.y + 30, 2, 0, Math.PI * 2);
ctx.arc(hide.x + 40, hide.y + 30, 2, 0, Math.PI * 2);
ctx.fill();
}
}
// Alert indicators
guardObjs.forEach(guard => {
if (guard.state === 'alert') {
ctx.save();
ctx.fillStyle = '#e74c3c';
ctx.font = 'bold 50px Arial';
ctx.textAlign = 'center';
ctx.shadowColor = '#000';
ctx.shadowBlur = 8;
ctx.fillText('!', guard.x + guard.w/2, guard.y - 20);
ctx.restore();
} else if (guard.state === 'search') {
ctx.save();
ctx.fillStyle = '#f39c12';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.shadowColor = '#000';
ctx.shadowBlur = 6;
ctx.fillText('?', guard.x + guard.w/2, guard.y - 20);
ctx.restore();
}
});
}
function drawBackground() {
if (currentLevel.bgType === 'image' && bgImageLoaded) {
ctx.drawImage(bgImage, 0, 0, CANVAS_W, CANVAS_H);
return;
}
// Procedural backgrounds
const skyGrad = ctx.createLinearGradient(0, 0, 0, CANVAS_H);
if (currentLevel.bgType === 'kitchen') {
skyGrad.addColorStop(0, '#8d6e63');
skyGrad.addColorStop(1, '#5d4037');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
// Kitchen details
ctx.fillStyle = '#4e342e';
ctx.fillRect(0, 200, CANVAS_W, 235);
ctx.fillStyle = '#795548';
for (let i = 0; i < CANVAS_W; i += 80) {
ctx.fillRect(i, 200, 75, 235);
}
} else if (currentLevel.bgType === 'library') {
skyGrad.addColorStop(0, '#3e2723');
skyGrad.addColorStop(1, '#1a1a1a');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
// Bookshelves
ctx.fillStyle = '#5d4037';
for (let i = 0; i < CANVAS_W; i += 120) {
ctx.fillRect(i, 100, 100, 335);
// Books
for (let j = 0; j < 5; j++) {
ctx.fillStyle = ['#e74c3c', '#3498db', '#2ecc71', '#f1c40f', '#9b59b6'][j];
ctx.fillRect(i + 10, 120 + j * 60, 80, 50);
}
}
} else if (currentLevel.bgType === 'dungeon') {
skyGrad.addColorStop(0, '#212121');
skyGrad.addColorStop(1, '#000000');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
// Stone walls
ctx.fillStyle = '#424242';
for (let i = 0; i < CANVAS_W; i += 60) {
for (let j = 0; j < 7; j++) {
ctx.fillRect(i + (j % 2) * 30, j * 65, 55, 60);
}
}
} else if (currentLevel.bgType === 'throne') {
skyGrad.addColorStop(0, '#4a148c');
skyGrad.addColorStop(1, '#1a0033');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
// Royal decorations
ctx.fillStyle = '#ffd700';
ctx.fillRect(0, 0, CANVAS_W, 20);
ctx.fillRect(0, CANVAS_H - 20, CANVAS_W, 20);
} else if (currentLevel.bgType === 'rooftop') {
skyGrad.addColorStop(0, '#1a237e');
skyGrad.addColorStop(1, '#0d1b3e');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
// Stars
ctx.fillStyle = '#fff';
for (let i = 0; i < 50; i++) {
const sx = (i * 137) % CANVAS_W;
const sy = (i * 89) % 300;
ctx.globalAlpha = 0.5 + Math.sin(Date.now() * 0.001 + i) * 0.3;
ctx.beginPath();
ctx.arc(sx, sy, 2, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// Moon
ctx.fillStyle = '#f5f5dc';
ctx.shadowColor = '#f5f5dc';
ctx.shadowBlur = 30;
ctx.beginPath();
ctx.arc(850, 100, 40, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
} else if (currentLevel.bgType === 'vault') {
skyGrad.addColorStop(0, '#263238');
skyGrad.addColorStop(1, '#0a0a0a');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
// Vault walls
ctx.fillStyle = '#37474f';
ctx.fillRect(0, 100, CANVAS_W, 335);
// Gold decorations
ctx.fillStyle = '#ffd700';
ctx.fillRect(0, 100, CANVAS_W, 10);
ctx.fillRect(0, 425, CANVAS_W, 10);
} else {
skyGrad.addColorStop(0, '#87ceeb');
skyGrad.addColorStop(1, '#e0f7fa');
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, CANVAS_W, CANVAS_H);
// Clouds
ctx.fillStyle = 'rgba(255,255,255,0.8)';
for (let i = 0; i < 5; i++) {
const cx = ((i * 200) + Date.now() * 0.02) % (CANVAS_W + 200) - 100;
ctx.beginPath();
ctx.arc(cx, 80 + i * 30, 40, 0, Math.PI * 2);
ctx.arc(cx + 30, 70 + i * 30, 35, 0, Math.PI * 2);
ctx.arc(cx + 60, 80 + i * 30, 30, 0, Math.PI * 2);
ctx.fill();
}
}
}
function drawGround() {
const groundY = currentLevel.groundY;
ctx.fillStyle = currentLevel.groundColor;
ctx.fillRect(0, groundY, CANVAS_W, CANVAS_H - groundY);
// Ground texture
ctx.fillStyle = 'rgba(0,0,0,0.1)';
for (let i = 0; i < CANVAS_W; i += 40) {
ctx.fillRect(i, groundY + 10, 35, 5);
}
// Top edge highlight
ctx.fillStyle = 'rgba(255,255,255,0.2)';
ctx.fillRect(0, groundY, CANVAS_W, 3);
}
function drawHideSpot(hide) {
ctx.save();
if (hide.type === 'barrel') {
ctx.fillStyle = '#8b4513';
ctx.beginPath();
ctx.roundRect(hide.x, hide.y, hide.w, hide.h, 10);
ctx.fill();
ctx.strokeStyle = '#2c3e50';
ctx.lineWidth = 4;
ctx.strokeRect(hide.x, hide.y + 15, hide.w, 5);
ctx.strokeRect(hide.x, hide.y + 70, hide.w, 5);
} else if (hide.type === 'cart') {
ctx.fillStyle = '#6d4c41';
ctx.fillRect(hide.x, hide.y + 20, hide.w, hide.h - 20);
ctx.fillStyle = '#5d4037';
ctx.fillRect(hide.x - 10, hide.y + 20, hide.w + 20, 15);
// Wheels
ctx.fillStyle = '#212121';
ctx.beginPath();
ctx.arc(hide.x + 15, hide.y + hide.h, 12, 0, Math.PI * 2);
ctx.arc(hide.x + hide.w - 15, hide.y + hide.h, 12, 0, Math.PI * 2);
ctx.fill();
} else if (hide.type === 'bookshelf') {
ctx.fillStyle = '#5d4037';
ctx.fillRect(hide.x, hide.y, hide.w, hide.h);
ctx.fillStyle = '#4e342e';
for (let i = 0; i < 4; i++) {
ctx.fillRect(hide.x, hide.y + 10 + i * 25, hide.w, 3);
}
// Books
const bookColors = ['#e74c3c', '#3498db', '#2ecc71', '#f1c40f'];
for (let i = 0; i < 4; i++) {
ctx.fillStyle = bookColors[i];
ctx.fillRect(hide.x + 5, hide.y + 15 + i * 25, 15, 20);
ctx.fillRect(hide.x + 25, hide.y + 15 + i * 25, 12, 20);
ctx.fillRect(hide.x + 42, hide.y + 15 + i * 25, 18, 20);
}
} else if (hide.type === 'chain') {
ctx.strokeStyle = '#757575';
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(hide.x + hide.w/2, hide.y);
ctx.lineTo(hide.x + hide.w/2, hide.y + hide.h);
ctx.stroke();
// Chain links
ctx.fillStyle = '#9e9e9e';
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.ellipse(hide.x + hide.w/2, hide.y + 10 + i * 20, 8, 12, 0, 0, Math.PI * 2);
ctx.fill();
}
} else if (hide.type === 'throne') {
ctx.fillStyle = '#ffd700';
ctx.fillRect(hide.x + 10, hide.y + 30, hide.w - 20, hide.h - 30);
ctx.fillStyle = '#b8860b';
ctx.fillRect(hide.x, hide.y, hide.w, 30);
// Cushion
ctx.fillStyle = '#8b0000';
ctx.fillRect(hide.x + 15, hide.y + 35, hide.w - 30, hide.h - 40);
} else if (hide.type === 'chimney') {
ctx.fillStyle = '#757575';
ctx.fillRect(hide.x, hide.y, hide.w, hide.h);
ctx.fillStyle = '#616161';
ctx.fillRect(hide.x - 5, hide.y, hide.w + 10, 15);
// Smoke
ctx.fillStyle = 'rgba(200,200,200,0.5)';
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.arc(hide.x + hide.w/2 + Math.sin(Date.now() * 0.001 + i) * 10, hide.y - 10 - i * 15, 10 + i * 3, 0, Math.PI * 2);
ctx.fill();
}
} else if (hide.type === 'pillar') {
ctx.fillStyle = '#616161';
ctx.fillRect(hide.x, hide.y, hide.w, hide.h);
ctx.fillStyle = '#757575';
ctx.fillRect(hide.x - 5, hide.y, hide.w + 10, 15);
ctx.fillRect(hide.x - 5, hide.y + hide.h - 15, hide.w + 10, 15);
// Cracks
ctx.strokeStyle = '#424242';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(hide.x + 20, hide.y + 30);
ctx.lineTo(hide.x + 25, hide.y + 60);
ctx.lineTo(hide.x + 15, hide.y + 80);
ctx.stroke();
}
ctx.restore();
}
function drawChest() {
ctx.save();
ctx.fillStyle = '#5c4033';
ctx.fillRect(chestObj.x, chestObj.y, chestObj.w, chestObj.h);
ctx.fillStyle = '#f1c40f';
ctx.fillRect(chestObj.x + 25, chestObj.y + 20, 15, 20);
if (chestObj.glass) {
ctx.strokeStyle = 'rgba(200,200,255,0.6)';
ctx.lineWidth = 3;
ctx.strokeRect(chestObj.x - 5, chestObj.y - 5, chestObj.w + 10, chestObj.h + 10);
ctx.fillStyle = 'rgba(200,200,255,0.2)';
ctx.fillRect(chestObj.x - 5, chestObj.y - 5, chestObj.w + 10, chestObj.h + 10);
}
if (chestObj.isOpen) {
ctx.fillStyle = '#3e2723';
ctx.fillRect(chestObj.x - 5, chestObj.y - 25, chestObj.w + 10, 25);
// Treasure glow
ctx.shadowColor = '#f1c40f';
ctx.shadowBlur = 20;
ctx.fillStyle = '#f1c40f';
ctx.fillRect(chestObj.x + 10, chestObj.y - 15, chestObj.w - 20, 10);
ctx.shadowBlur = 0;
} else {
ctx.fillStyle = '#8d6e63';
ctx.fillRect(chestObj.x, chestObj.y, chestObj.w, 20);
}
ctx.restore();
}
function drawKey() {
ctx.save();
const bobY = Math.sin(Date.now() * 0.003) * 5;
ctx.translate(keyObj.x + 10, keyObj.y + 10 + bobY);
ctx.shadowColor = '#f1c40f';
ctx.shadowBlur = 15;
ctx.fillStyle = '#f1c40f';
// Key head
ctx.beginPath();
ctx.arc(0, 0, 10, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(0, 0, 4, 0, Math.PI * 2);
ctx.fill();
// Key shaft
ctx.fillStyle = '#f1c40f';
ctx.fillRect(-3, 10, 6, 25);
// Key teeth
ctx.fillRect(3, 20, 8, 4);
ctx.fillRect(3, 28, 8, 4);
ctx.shadowBlur = 0;
ctx.restore();
}
function drawDoor() {
ctx.save();
if (doorObj.vault) {
// Vault door
ctx.fillStyle = '#424242';
ctx.fillRect(doorObj.x, doorObj.y, doorObj.w, doorObj.h);
ctx.fillStyle = '#616161';
ctx.beginPath();
ctx.arc(doorObj.x + doorObj.w/2, doorObj.y + doorObj.h/2, 30, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffd700';
ctx.beginPath();
ctx.arc(doorObj.x + doorObj.w/2, doorObj.y + doorObj.h/2, 10, 0, Math.PI * 2);
ctx.fill();
} else if (doorObj.secret) {
ctx.fillStyle = '#5d4037';
ctx.fillRect(doorObj.x, doorObj.y, doorObj.w, doorObj.h);
ctx.fillStyle = '#3e2723';
ctx.fillRect(doorObj.x + 10, doorObj.y + 10, doorObj.w - 20, doorObj.h - 10);
// Secret symbol
ctx.fillStyle = '#ffd700';
ctx.font = '30px Arial';
ctx.textAlign = 'center';
ctx.fillText('🔮', doorObj.x + doorObj.w/2, doorObj.y + doorObj.h/2 + 10);
} else if (doorObj.grate) {
ctx.strokeStyle = '#757575';
ctx.lineWidth = 4;
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.moveTo(doorObj.x + 10 + i * 12, doorObj.y);
ctx.lineTo(doorObj.x + 10 + i * 12, doorObj.y + doorObj.h);
ctx.stroke();
}
for (let i = 0; i < 8; i++) {
ctx.beginPath();
ctx.moveTo(doorObj.x, doorObj.y + 10 + i * 15);
ctx.lineTo(doorObj.x + doorObj.w, doorObj.y + 10 + i * 15);
ctx.stroke();
}
} else if (doorObj.chimney) {
ctx.fillStyle = '#757575';
ctx.fillRect(doorObj.x, doorObj.y, doorObj.w, doorObj.h);
ctx.fillStyle = '#212121';
ctx.fillRect(doorObj.x + 10, doorObj.y + 10, doorObj.w - 20, doorObj.h - 10);
} else {
ctx.fillStyle = '#7f8c8d';
ctx.fillRect(doorObj.x, doorObj.y, doorObj.w, doorObj.h);
ctx.fillStyle = thiefObj.hasKey ? '#2ecc71' : '#3e2723';
ctx.fillRect(doorObj.x + 10, doorObj.y + 10, doorObj.w - 20, doorObj.h - 10);
ctx.fillStyle = '#d35400';
ctx.beginPath();
ctx.arc(doorObj.x + 25, doorObj.y + 70, 7, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
function drawDog() {
ctx.save();
ctx.translate(dogObj.x + 30, dogObj.y + 40);
// Dog body
ctx.fillStyle = dogObj.awake ? '#8b4513' : '#a0522d';
ctx.beginPath();
ctx.ellipse(0, 0, 35, 25, 0, 0, Math.PI * 2);
ctx.fill();
// Head
ctx.beginPath();
ctx.arc(25, -15, 18, 0, Math.PI * 2);
ctx.fill();
// Ears
ctx.fillStyle = '#654321';
ctx.beginPath();
ctx.ellipse(15, -30, 8, 12, -0.3, 0, Math.PI * 2);
ctx.ellipse(35, -30, 8, 12, 0.3, 0, Math.PI * 2);
ctx.fill();
// Eyes
if (dogObj.awake) {
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(20, -18, 5, 0, Math.PI * 2);
ctx.arc(32, -18, 5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(21, -18, 2.5, 0, Math.PI * 2);
ctx.arc(33, -18, 2.5, 0, Math.PI * 2);
ctx.fill();
} else {
// Sleeping eyes (closed)
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(20, -16, 4, 0, Math.PI);
ctx.arc(32, -16, 4, 0, Math.PI);
ctx.stroke();
// Zzz
ctx.fillStyle = '#fff';
ctx.font = 'bold 16px Arial';
ctx.fillText('Z', 40, -30);
ctx.font = 'bold 12px Arial';
ctx.fillText('z', 50, -40);
}
// Nose
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(28, -10, 4, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawGuard(guard) {
ctx.save();
// Sight cone
if (guard.state === 'patrol' || guard.state === 'search') {
const sightGrad = ctx.createLinearGradient(
guard.x + (guard.dir === 1 ? guard.w : 0), guard.y + 40,
guard.x + (guard.dir === 1 ? guard.w : 0) + (guard.sightRange * guard.dir), guard.y + 40
);
const coneColor = guard.lantern ? 'rgba(255, 200, 100, 0.3)' : 'rgba(241, 196, 15, 0.3)';
sightGrad.addColorStop(0, coneColor);
sightGrad.addColorStop(1, 'rgba(241, 196, 15, 0)');
ctx.fillStyle = sightGrad;
ctx.beginPath();
ctx.moveTo(guard.x + (guard.dir === 1 ? guard.w : 0), guard.y + 25);
ctx.lineTo(guard.x + (guard.dir === 1 ? guard.w : 0) + (guard.sightRange * guard.dir), guard.y - 15);
ctx.lineTo(guard.x + (guard.dir === 1 ? guard.w : 0) + (guard.sightRange * guard.dir), guard.y + 85);
ctx.closePath();
ctx.fill();
}
ctx.translate(guard.x + guard.w / 2, guard.y + guard.h);
ctx.scale(guard.dir, guard.scaleY);
// Body/Armor
const armorColor = guard.elite ? '#c0392b' : guard.royal ? '#8e44ad' : '#bdc3c7';
ctx.fillStyle = armorColor;
ctx.beginPath();
ctx.roundRect(-guard.w / 2, -guard.h, guard.w, guard.h, [15, 15, 0, 0]);
ctx.fill();
// Cape
ctx.fillStyle = guard.elite ? '#2c3e50' : '#c0392b';
ctx.fillRect(-guard.w / 2 - 5, -guard.h + 30, 8, 70);
// Face
ctx.fillStyle = '#ffdbac';
ctx.fillRect(-guard.w / 2 + 10, -guard.h + 15, guard.w - 20, 30);
// Mustache
ctx.fillStyle = '#2c3e50';
ctx.fillRect(-guard.w / 2 + 15, -guard.h + 32, guard.w - 30, 8);
// Helmet
ctx.fillStyle = guard.elite ? '#2c3e50' : '#7f8c8d';
ctx.beginPath();
ctx.arc(0, -guard.h + 10, guard.w/2 - 2, Math.PI, 0);
ctx.fill();
// Plume
ctx.fillStyle = guard.elite ? '#e74c3c' : '#e74c3c';
ctx.beginPath();
ctx.ellipse(0, -guard.h - 10, 8, 20, 0, 0, Math.PI * 2);
ctx.fill();
// Eye
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(10, -guard.h + 25, 6, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(11, -guard.h + 25, 3, 0, Math.PI * 2);
ctx.fill();
// Lantern for library guard
if (guard.lantern) {
ctx.fillStyle = '#f39c12';
ctx.shadowColor = '#f39c12';
ctx.shadowBlur = 20;
ctx.beginPath();
ctx.arc(20, -guard.h + 50, 12, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(20, -guard.h + 50, 6, 0, Math.PI * 2);
ctx.fill();
}
// Spear
ctx.strokeStyle = '#795548';
ctx.lineWidth = 5;
ctx.beginPath();
ctx.moveTo(15, -10);
ctx.lineTo(15, -guard.h - 20);
ctx.stroke();
ctx.fillStyle = '#95a5a6';
ctx.beginPath();
ctx.moveTo(15, -guard.h - 35);
ctx.lineTo(8, -guard.h - 18);
ctx.lineTo(22, -guard.h - 18);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function drawThief() {
ctx.save();
ctx.translate(thiefObj.x + thiefObj.w / 2, thiefObj.y + thiefObj.h);
ctx.scale(thiefObj.dir, thiefObj.scaleY);
// Cloak
ctx.fillStyle = '#2c3e50';
ctx.shadowColor = 'rgba(0,0,0,0.5)';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.roundRect(-thiefObj.w / 2, -thiefObj.h, thiefObj.w, thiefObj.h, [25, 25, 0, 0]);
ctx.fill();
ctx.shadowBlur = 0;
// Scarf
ctx.fillStyle = '#e74c3c';
ctx.fillRect(-thiefObj.w / 2 + 5, -thiefObj.h + 55, thiefObj.w - 10, 10);
// Face
ctx.fillStyle = '#ffdbac';
ctx.beginPath();
ctx.arc(0, -thiefObj.h + 35, 18, 0, Math.PI * 2);
ctx.fill();
// Mask/Hat
ctx.fillStyle = '#34495e';
ctx.beginPath();
ctx.arc(0, -thiefObj.h + 30, 20, Math.PI, 0);
ctx.fill();
// Eyes
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(6, -thiefObj.h + 35, 5, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(7, -thiefObj.h + 35, 2.5, 0, Math.PI * 2);
ctx.fill();
// Bag
ctx.fillStyle = '#855e42';
ctx.beginPath();
ctx.arc(-18, -thiefObj.h + 60, 10, 0, Math.PI * 2);
ctx.fill();
// Key indicator
if (thiefObj.hasKey) {
ctx.fillStyle = '#f1c40f';
ctx.shadowColor = '#f1c40f';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(15, -thiefObj.h + 50, 6, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
ctx.restore();
}
// --- Modal System ---
let modalCallback = null;
function showModal(title, subtitle, type, callback = null) {
const modal = document.getElementById('modal-overlay');
const titleEl = document.getElementById('modal-title');
const subtitleEl = document.getElementById('modal-subtitle');
const btn = document.getElementById('modal-btn');
titleEl.textContent = title;
titleEl.className = type + '-title';
subtitleEl.textContent = subtitle;
if (type === 'caught') {
btn.textContent = 'تلاش دوباره';
modalCallback = () => initLevel(currentLevelIdx);
} else if (type === 'win') {
btn.textContent = 'مرحله بعدی';
modalCallback = callback || (() => initLevel(currentLevelIdx + 1));
} else if (type === 'complete') {
btn.textContent = 'شروع دوباره';
modalCallback = callback || (() => { totalStars = 0; initLevel(0); });
}
modal.classList.add('active');
}
function hideModal() {
document.getElementById('modal-overlay').classList.remove('active');
}
document.getElementById('modal-btn').addEventListener('click', () => {
if (modalCallback) modalCallback();
});
// --- Game Loop ---
function gameLoop(timestamp) {
const dt = timestamp - lastTime;
lastTime = timestamp;
update(dt);
draw();
requestAnimationFrame(gameLoop);
}
// --- Init ---
initLevel(0);
requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>