LukasBe commited on
Commit
f82ba54
·
verified ·
1 Parent(s): e323685

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1726 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Dungeon Quest
3
- emoji: 🐨
4
- colorFrom: green
5
- colorTo: pink
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: dungeon-quest
3
+ emoji: 🐳
4
+ colorFrom: gray
5
+ colorTo: red
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1726 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Prince of Persia: Forgotten Palace</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=MedievalSharp&display=swap');
10
+
11
+ :root {
12
+ --primary-color: #d4a76a;
13
+ --secondary-color: #8b4513;
14
+ --dark-color: #1a1a1a;
15
+ --light-color: #f5e6c8;
16
+ --danger-color: #c41e3a;
17
+ }
18
+
19
+ body {
20
+ font-family: 'Cinzel', serif;
21
+ background-color: var(--dark-color);
22
+ color: var(--light-color);
23
+ overflow: hidden;
24
+ touch-action: none;
25
+ user-select: none;
26
+ }
27
+
28
+ #game-container {
29
+ position: relative;
30
+ width: 100vw;
31
+ height: 100vh;
32
+ overflow: hidden;
33
+ }
34
+
35
+ #game-canvas {
36
+ position: absolute;
37
+ top: 0;
38
+ left: 0;
39
+ width: 100%;
40
+ height: 100%;
41
+ background-color: #222;
42
+ }
43
+
44
+ #ui-overlay {
45
+ position: absolute;
46
+ top: 0;
47
+ left: 0;
48
+ width: 100%;
49
+ height: 100%;
50
+ pointer-events: none;
51
+ }
52
+
53
+ .health-bar {
54
+ position: absolute;
55
+ top: 20px;
56
+ left: 20px;
57
+ width: 200px;
58
+ height: 30px;
59
+ background-color: rgba(0, 0, 0, 0.5);
60
+ border: 2px solid var(--primary-color);
61
+ border-radius: 5px;
62
+ overflow: hidden;
63
+ }
64
+
65
+ .health-fill {
66
+ height: 100%;
67
+ width: 100%;
68
+ background-color: var(--danger-color);
69
+ transition: width 0.3s ease;
70
+ }
71
+
72
+ .controls {
73
+ position: absolute;
74
+ bottom: 20px;
75
+ width: 100%;
76
+ display: flex;
77
+ justify-content: space-between;
78
+ padding: 0 20px;
79
+ pointer-events: auto;
80
+ }
81
+
82
+ .mobile-control {
83
+ width: 70px;
84
+ height: 70px;
85
+ background-color: rgba(255, 255, 255, 0.2);
86
+ border-radius: 50%;
87
+ display: flex;
88
+ justify-content: center;
89
+ align-items: center;
90
+ font-size: 24px;
91
+ color: white;
92
+ touch-action: manipulation;
93
+ }
94
+
95
+ #start-screen, #game-over-screen, #victory-screen {
96
+ position: absolute;
97
+ top: 0;
98
+ left: 0;
99
+ width: 100%;
100
+ height: 100%;
101
+ display: flex;
102
+ flex-direction: column;
103
+ justify-content: center;
104
+ align-items: center;
105
+ background-color: rgba(0, 0, 0, 0.8);
106
+ z-index: 100;
107
+ }
108
+
109
+ .title {
110
+ font-size: 4rem;
111
+ color: var(--primary-color);
112
+ text-shadow: 0 0 10px rgba(212, 167, 106, 0.5);
113
+ margin-bottom: 2rem;
114
+ font-weight: 700;
115
+ letter-spacing: 3px;
116
+ }
117
+
118
+ .btn {
119
+ background-color: var(--primary-color);
120
+ color: var(--dark-color);
121
+ padding: 12px 30px;
122
+ border: none;
123
+ border-radius: 5px;
124
+ font-size: 1.2rem;
125
+ font-weight: bold;
126
+ cursor: pointer;
127
+ transition: all 0.3s ease;
128
+ margin-top: 20px;
129
+ font-family: 'Cinzel', serif;
130
+ }
131
+
132
+ .btn:hover {
133
+ background-color: var(--light-color);
134
+ transform: translateY(-2px);
135
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
136
+ }
137
+
138
+ .story-text {
139
+ max-width: 600px;
140
+ text-align: center;
141
+ margin-bottom: 30px;
142
+ line-height: 1.6;
143
+ font-size: 1.1rem;
144
+ }
145
+
146
+ #touch-controls {
147
+ display: none;
148
+ }
149
+
150
+ @media (max-width: 768px) {
151
+ #touch-controls {
152
+ display: flex;
153
+ }
154
+
155
+ .title {
156
+ font-size: 2.5rem;
157
+ }
158
+ }
159
+
160
+ /* Animation for torch flicker */
161
+ @keyframes torch-flicker {
162
+ 0%, 100% { opacity: 0.8; }
163
+ 25% { opacity: 1; }
164
+ 50% { opacity: 0.7; }
165
+ 75% { opacity: 0.9; }
166
+ }
167
+
168
+ /* Animation for sword slash */
169
+ @keyframes sword-slash {
170
+ 0% { transform: rotate(0deg); }
171
+ 50% { transform: rotate(90deg); }
172
+ 100% { transform: rotate(0deg); }
173
+ }
174
+
175
+ /* Dust particle animation */
176
+ @keyframes dust-particle {
177
+ 0% { transform: translateY(0) scale(1); opacity: 1; }
178
+ 100% { transform: translateY(-50px) scale(0.5); opacity: 0; }
179
+ }
180
+ </style>
181
+ </head>
182
+ <body>
183
+ <div id="game-container">
184
+ <canvas id="game-canvas"></canvas>
185
+
186
+ <div id="ui-overlay">
187
+ <div class="health-bar">
188
+ <div id="health-fill" class="health-fill" style="width: 100%;"></div>
189
+ </div>
190
+
191
+ <div id="touch-controls" class="controls">
192
+ <div id="left-btn" class="mobile-control">←</div>
193
+ <div id="right-btn" class="mobile-control">→</div>
194
+ <div id="jump-btn" class="mobile-control">↑</div>
195
+ <div id="attack-btn" class="mobile-control">⚔️</div>
196
+ </div>
197
+ </div>
198
+
199
+ <div id="start-screen">
200
+ <h1 class="title">PRINCE OF PERSIA</h1>
201
+ <p class="story-text">The ancient palace holds many secrets... and dangers. You must navigate its treacherous halls, overcome deadly traps, and defeat the guards that stand between you and freedom.</p>
202
+ <button id="start-btn" class="btn">BEGIN YOUR QUEST</button>
203
+ </div>
204
+
205
+ <div id="game-over-screen" style="display: none;">
206
+ <h1 class="title">DEFEATED</h1>
207
+ <p class="story-text">The palace has claimed another victim. Will you rise to the challenge once more?</p>
208
+ <button id="restart-btn" class="btn">TRY AGAIN</button>
209
+ </div>
210
+
211
+ <div id="victory-screen" style="display: none;">
212
+ <h1 class="title">VICTORY</h1>
213
+ <p class="story-text">You have escaped the forgotten palace... but your journey is far from over.</p>
214
+ <button id="next-level-btn" class="btn">CONTINUE</button>
215
+ </div>
216
+ </div>
217
+
218
+ <script>
219
+ // Game Constants
220
+ const GAME_WIDTH = 1280;
221
+ const GAME_HEIGHT = 720;
222
+ const GRAVITY = 0.5;
223
+ const PLAYER_SPEED = 5;
224
+ const JUMP_FORCE = -12;
225
+ const PLAYER_WIDTH = 50;
226
+ const PLAYER_HEIGHT = 80;
227
+ const TILE_SIZE = 40;
228
+
229
+ // Game State
230
+ let gameState = {
231
+ player: {
232
+ x: 100,
233
+ y: GAME_HEIGHT - PLAYER_HEIGHT - 100,
234
+ width: PLAYER_WIDTH,
235
+ height: PLAYER_HEIGHT,
236
+ velocityX: 0,
237
+ velocityY: 0,
238
+ isJumping: false,
239
+ isAttacking: false,
240
+ isBlocking: false,
241
+ health: 100,
242
+ maxHealth: 100,
243
+ facingRight: true,
244
+ attackCooldown: 0,
245
+ invincible: false,
246
+ invincibleTimer: 0
247
+ },
248
+ enemies: [],
249
+ platforms: [],
250
+ traps: [],
251
+ torches: [],
252
+ particles: [],
253
+ camera: {
254
+ x: 0,
255
+ y: 0,
256
+ targetX: 0,
257
+ targetY: 0,
258
+ zoom: 1
259
+ },
260
+ keys: {
261
+ left: false,
262
+ right: false,
263
+ up: false,
264
+ down: false,
265
+ attack: false,
266
+ block: false
267
+ },
268
+ gameStarted: false,
269
+ gameOver: false,
270
+ victory: false,
271
+ levelBounds: {
272
+ left: 0,
273
+ right: 3000,
274
+ top: 0,
275
+ bottom: GAME_HEIGHT
276
+ },
277
+ checkpoints: [],
278
+ currentCheckpoint: 0,
279
+ lastTime: 0,
280
+ deltaTime: 0
281
+ };
282
+
283
+ // DOM Elements
284
+ const canvas = document.getElementById('game-canvas');
285
+ const ctx = canvas.getContext('2d');
286
+ const startScreen = document.getElementById('start-screen');
287
+ const gameOverScreen = document.getElementById('game-over-screen');
288
+ const victoryScreen = document.getElementById('victory-screen');
289
+ const startBtn = document.getElementById('start-btn');
290
+ const restartBtn = document.getElementById('restart-btn');
291
+ const nextLevelBtn = document.getElementById('next-level-btn');
292
+ const healthFill = document.getElementById('health-fill');
293
+ const leftBtn = document.getElementById('left-btn');
294
+ const rightBtn = document.getElementById('right-btn');
295
+ const jumpBtn = document.getElementById('jump-btn');
296
+ const attackBtn = document.getElementById('attack-btn');
297
+
298
+ // Initialize Game
299
+ function initGame() {
300
+ // Set canvas size
301
+ resizeCanvas();
302
+ window.addEventListener('resize', resizeCanvas);
303
+
304
+ // Initialize level
305
+ createLevel();
306
+
307
+ // Event listeners
308
+ startBtn.addEventListener('click', startGame);
309
+ restartBtn.addEventListener('click', restartGame);
310
+ nextLevelBtn.addEventListener('click', nextLevel);
311
+
312
+ // Keyboard controls
313
+ document.addEventListener('keydown', handleKeyDown);
314
+ document.addEventListener('keyup', handleKeyUp);
315
+
316
+ // Touch controls
317
+ leftBtn.addEventListener('touchstart', () => gameState.keys.left = true);
318
+ leftBtn.addEventListener('touchend', () => gameState.keys.left = false);
319
+ rightBtn.addEventListener('touchstart', () => gameState.keys.right = true);
320
+ rightBtn.addEventListener('touchend', () => gameState.keys.right = false);
321
+ jumpBtn.addEventListener('touchstart', () => gameState.keys.up = true);
322
+ jumpBtn.addEventListener('touchend', () => gameState.keys.up = false);
323
+ attackBtn.addEventListener('touchstart', () => gameState.keys.attack = true);
324
+ attackBtn.addEventListener('touchend', () => gameState.keys.attack = false);
325
+
326
+ // Start game loop
327
+ requestAnimationFrame(gameLoop);
328
+ }
329
+
330
+ // Resize canvas to fit window
331
+ function resizeCanvas() {
332
+ canvas.width = window.innerWidth;
333
+ canvas.height = window.innerHeight;
334
+ }
335
+
336
+ // Create level elements
337
+ function createLevel() {
338
+ // Platforms
339
+ gameState.platforms = [
340
+ // Ground
341
+ { x: 0, y: GAME_HEIGHT - 50, width: 3000, height: 50 },
342
+
343
+ // First platform
344
+ { x: 300, y: GAME_HEIGHT - 150, width: 200, height: 20 },
345
+
346
+ // Second platform (collapsing)
347
+ { x: 600, y: GAME_HEIGHT - 200, width: 150, height: 20, collapsing: true, collapseTimer: 0 },
348
+
349
+ // Third platform
350
+ { x: 900, y: GAME_HEIGHT - 250, width: 200, height: 20 },
351
+
352
+ // Final platform before exit
353
+ { x: 2500, y: GAME_HEIGHT - 300, width: 200, height: 20 }
354
+ ];
355
+
356
+ // Traps
357
+ gameState.traps = [
358
+ // Spike pit
359
+ { x: 800, y: GAME_HEIGHT - 50, width: 100, height: 50, type: 'spikes' },
360
+
361
+ // Rolling blade
362
+ { x: 1200, y: GAME_HEIGHT - 300, width: 20, height: 200, type: 'blade', direction: 1, speed: 3 },
363
+
364
+ // Pressure plate
365
+ { x: 1500, y: GAME_HEIGHT - 30, width: 40, height: 20, type: 'pressurePlate', triggered: false }
366
+ ];
367
+
368
+ // Torches
369
+ gameState.torches = [
370
+ { x: 200, y: GAME_HEIGHT - 150, lit: true, flicker: 0 },
371
+ { x: 500, y: GAME_HEIGHT - 200, lit: true, flicker: 0 },
372
+ { x: 850, y: GAME_HEIGHT - 250, lit: true, flicker: 0 },
373
+ { x: 1200, y: GAME_HEIGHT - 300, lit: true, flicker: 0 }
374
+ ];
375
+
376
+ // Enemies
377
+ gameState.enemies = [
378
+ // First guard
379
+ {
380
+ x: 400,
381
+ y: GAME_HEIGHT - 170,
382
+ width: 40,
383
+ height: 70,
384
+ health: 50,
385
+ speed: 2,
386
+ direction: -1,
387
+ isAttacking: false,
388
+ attackCooldown: 0,
389
+ type: 'guard'
390
+ },
391
+
392
+ // Second guard
393
+ {
394
+ x: 950,
395
+ y: GAME_HEIGHT - 270,
396
+ width: 40,
397
+ height: 70,
398
+ health: 50,
399
+ speed: 2,
400
+ direction: -1,
401
+ isAttacking: false,
402
+ attackCooldown: 0,
403
+ type: 'guard'
404
+ },
405
+
406
+ // Elite guard (final enemy)
407
+ {
408
+ x: 2550,
409
+ y: GAME_HEIGHT - 320,
410
+ width: 50,
411
+ height: 80,
412
+ health: 100,
413
+ speed: 3,
414
+ direction: -1,
415
+ isAttacking: false,
416
+ attackCooldown: 0,
417
+ type: 'elite'
418
+ }
419
+ ];
420
+
421
+ // Checkpoints
422
+ gameState.checkpoints = [
423
+ { x: 100, y: GAME_HEIGHT - PLAYER_HEIGHT - 100 },
424
+ { x: 700, y: GAME_HEIGHT - 220 },
425
+ { x: 1600, y: GAME_HEIGHT - 270 }
426
+ ];
427
+
428
+ gameState.currentCheckpoint = 0;
429
+ }
430
+
431
+ // Start game
432
+ function startGame() {
433
+ gameState.gameStarted = true;
434
+ startScreen.style.display = 'none';
435
+ gameState.player.x = gameState.checkpoints[0].x;
436
+ gameState.player.y = gameState.checkpoints[0].y;
437
+ }
438
+
439
+ // Restart game
440
+ function restartGame() {
441
+ gameState.gameOver = false;
442
+ gameOverScreen.style.display = 'none';
443
+ gameState.player.health = gameState.player.maxHealth;
444
+ gameState.player.x = gameState.checkpoints[gameState.currentCheckpoint].x;
445
+ gameState.player.y = gameState.checkpoints[gameState.currentCheckpoint].y;
446
+ gameState.player.velocityX = 0;
447
+ gameState.player.velocityY = 0;
448
+ gameState.player.isJumping = false;
449
+ gameState.player.isAttacking = false;
450
+ gameState.player.isBlocking = false;
451
+ gameState.player.invincible = false;
452
+
453
+ // Reset enemies
454
+ gameState.enemies.forEach(enemy => {
455
+ enemy.health = enemy.type === 'elite' ? 100 : 50;
456
+ });
457
+
458
+ // Reset traps
459
+ gameState.traps.forEach(trap => {
460
+ if (trap.type === 'pressurePlate') {
461
+ trap.triggered = false;
462
+ }
463
+ });
464
+
465
+ // Reset collapsing platforms
466
+ gameState.platforms.forEach(platform => {
467
+ if (platform.collapsing) {
468
+ platform.collapseTimer = 0;
469
+ }
470
+ });
471
+ }
472
+
473
+ // Next level (placeholder)
474
+ function nextLevel() {
475
+ victoryScreen.style.display = 'none';
476
+ gameState.victory = false;
477
+ // In a full game, this would load the next level
478
+ alert("This concludes the demo. Thanks for playing!");
479
+ }
480
+
481
+ // Handle keyboard input
482
+ function handleKeyDown(e) {
483
+ switch(e.key) {
484
+ case 'ArrowLeft':
485
+ case 'a':
486
+ gameState.keys.left = true;
487
+ break;
488
+ case 'ArrowRight':
489
+ case 'd':
490
+ gameState.keys.right = true;
491
+ break;
492
+ case 'ArrowUp':
493
+ case 'w':
494
+ case ' ':
495
+ gameState.keys.up = true;
496
+ break;
497
+ case 'ArrowDown':
498
+ case 's':
499
+ gameState.keys.down = true;
500
+ break;
501
+ case 'z':
502
+ gameState.keys.attack = true;
503
+ break;
504
+ case 'x':
505
+ gameState.keys.block = true;
506
+ break;
507
+ }
508
+ }
509
+
510
+ function handleKeyUp(e) {
511
+ switch(e.key) {
512
+ case 'ArrowLeft':
513
+ case 'a':
514
+ gameState.keys.left = false;
515
+ break;
516
+ case 'ArrowRight':
517
+ case 'd':
518
+ gameState.keys.right = false;
519
+ break;
520
+ case 'ArrowUp':
521
+ case 'w':
522
+ case ' ':
523
+ gameState.keys.up = false;
524
+ break;
525
+ case 'ArrowDown':
526
+ case 's':
527
+ gameState.keys.down = false;
528
+ break;
529
+ case 'z':
530
+ gameState.keys.attack = false;
531
+ break;
532
+ case 'x':
533
+ gameState.keys.block = false;
534
+ break;
535
+ }
536
+ }
537
+
538
+ // Game loop
539
+ function gameLoop(timestamp) {
540
+ if (!gameState.lastTime) {
541
+ gameState.lastTime = timestamp;
542
+ }
543
+
544
+ gameState.deltaTime = (timestamp - gameState.lastTime) / 1000;
545
+ gameState.lastTime = timestamp;
546
+
547
+ if (gameState.gameStarted && !gameState.gameOver && !gameState.victory) {
548
+ update();
549
+ }
550
+
551
+ render();
552
+
553
+ requestAnimationFrame(gameLoop);
554
+ }
555
+
556
+ // Update game state
557
+ function update() {
558
+ const { player, enemies, platforms, traps, torches, particles } = gameState;
559
+
560
+ // Update player
561
+ updatePlayer();
562
+
563
+ // Update enemies
564
+ updateEnemies();
565
+
566
+ // Update traps
567
+ updateTraps();
568
+
569
+ // Update platforms
570
+ updatePlatforms();
571
+
572
+ // Update torches
573
+ updateTorches();
574
+
575
+ // Update particles
576
+ updateParticles();
577
+
578
+ // Update camera
579
+ updateCamera();
580
+
581
+ // Check for victory condition
582
+ if (player.x > 2800 && player.y < GAME_HEIGHT - 200) {
583
+ gameState.victory = true;
584
+ victoryScreen.style.display = 'flex';
585
+ }
586
+
587
+ // Check for game over
588
+ if (player.health <= 0) {
589
+ gameState.gameOver = true;
590
+ gameOverScreen.style.display = 'flex';
591
+ }
592
+ }
593
+
594
+ // Update player
595
+ function updatePlayer() {
596
+ const { player, keys } = gameState;
597
+
598
+ // Horizontal movement
599
+ if (keys.left) {
600
+ player.velocityX = -PLAYER_SPEED;
601
+ player.facingRight = false;
602
+ } else if (keys.right) {
603
+ player.velocityX = PLAYER_SPEED;
604
+ player.facingRight = true;
605
+ } else {
606
+ // Apply friction
607
+ player.velocityX *= 0.7;
608
+ if (Math.abs(player.velocityX) < 0.5) player.velocityX = 0;
609
+ }
610
+
611
+ // Jumping
612
+ if (keys.up && !player.isJumping) {
613
+ player.velocityY = JUMP_FORCE;
614
+ player.isJumping = true;
615
+ createDustParticles(player.x + player.width/2, player.y + player.height, 10);
616
+ }
617
+
618
+ // Apply gravity
619
+ player.velocityY += GRAVITY;
620
+
621
+ // Update position
622
+ player.x += player.velocityX;
623
+ player.y += player.velocityY;
624
+
625
+ // Check for collisions with platforms
626
+ checkPlatformCollisions();
627
+
628
+ // Check for collisions with enemies
629
+ checkEnemyCollisions();
630
+
631
+ // Check for collisions with traps
632
+ checkTrapCollisions();
633
+
634
+ // Check for ledge grabbing
635
+ checkLedgeGrab();
636
+
637
+ // Update attack cooldown
638
+ if (player.attackCooldown > 0) {
639
+ player.attackCooldown -= gameState.deltaTime;
640
+ } else if (keys.attack) {
641
+ player.isAttacking = true;
642
+ player.attackCooldown = 0.5;
643
+
644
+ // Create sword slash effect
645
+ createSwordSlash();
646
+
647
+ // Check for enemy hits
648
+ checkAttackHit();
649
+
650
+ // Reset after animation would complete
651
+ setTimeout(() => {
652
+ player.isAttacking = false;
653
+ }, 200);
654
+ }
655
+
656
+ // Update blocking
657
+ player.isBlocking = keys.block;
658
+
659
+ // Update invincibility timer
660
+ if (player.invincible) {
661
+ player.invincibleTimer -= gameState.deltaTime;
662
+ if (player.invincibleTimer <= 0) {
663
+ player.invincible = false;
664
+ }
665
+ }
666
+
667
+ // Keep player within bounds
668
+ if (player.x < 0) player.x = 0;
669
+ if (player.x > gameState.levelBounds.right - player.width) {
670
+ player.x = gameState.levelBounds.right - player.width;
671
+ }
672
+ if (player.y > gameState.levelBounds.bottom - player.height) {
673
+ player.y = gameState.levelBounds.bottom - player.height;
674
+ player.velocityY = 0;
675
+ player.isJumping = false;
676
+ }
677
+
678
+ // Update health bar
679
+ healthFill.style.width = `${(player.health / player.maxHealth) * 100}%`;
680
+ }
681
+
682
+ // Check for platform collisions
683
+ function checkPlatformCollisions() {
684
+ const { player, platforms } = gameState;
685
+
686
+ player.isJumping = true;
687
+
688
+ for (const platform of platforms) {
689
+ // Skip if platform is collapsing and has collapsed
690
+ if (platform.collapsing && platform.collapseTimer >= 1) continue;
691
+
692
+ if (player.x < platform.x + platform.width &&
693
+ player.x + player.width > platform.x &&
694
+ player.y < platform.y + platform.height &&
695
+ player.y + player.height > platform.y) {
696
+
697
+ // Check if player is landing on top of platform
698
+ if (player.velocityY > 0 && player.y + player.height - player.velocityY <= platform.y) {
699
+ player.y = platform.y - player.height;
700
+ player.velocityY = 0;
701
+ player.isJumping = false;
702
+
703
+ // Create dust particles when landing
704
+ if (Math.abs(player.velocityY) > 5) {
705
+ createDustParticles(player.x + player.width/2, player.y + player.height, 5);
706
+ }
707
+ }
708
+ // Check if player hits platform from below
709
+ else if (player.velocityY < 0 && player.y - player.velocityY >= platform.y + platform.height) {
710
+ player.y = platform.y + platform.height;
711
+ player.velocityY = 0;
712
+ }
713
+ // Check if player hits platform from the left
714
+ else if (player.velocityX > 0 && player.x + player.width - player.velocityX <= platform.x) {
715
+ player.x = platform.x - player.width;
716
+ player.velocityX = 0;
717
+ }
718
+ // Check if player hits platform from the right
719
+ else if (player.velocityX < 0 && player.x - player.velocityX >= platform.x + platform.width) {
720
+ player.x = platform.x + platform.width;
721
+ player.velocityX = 0;
722
+ }
723
+ }
724
+ }
725
+ }
726
+
727
+ // Check for enemy collisions
728
+ function checkEnemyCollisions() {
729
+ const { player, enemies } = gameState;
730
+
731
+ for (const enemy of enemies) {
732
+ if (enemy.health <= 0) continue;
733
+
734
+ // Check collision with player
735
+ if (player.x < enemy.x + enemy.width &&
736
+ player.x + player.width > enemy.x &&
737
+ player.y < enemy.y + enemy.height &&
738
+ player.y + player.height > enemy.y) {
739
+
740
+ // If player is attacking and facing the enemy
741
+ if (player.isAttacking &&
742
+ ((player.facingRight && player.x < enemy.x) ||
743
+ (!player.facingRight && player.x > enemy.x))) {
744
+ // Enemy takes damage (handled in attack check)
745
+ }
746
+ // If player is blocking
747
+ else if (player.isBlocking) {
748
+ // Create spark effect
749
+ createSparkEffect(enemy.x + enemy.width/2, enemy.y + enemy.height/2);
750
+
751
+ // Small knockback
752
+ if (player.x < enemy.x) {
753
+ player.velocityX = -5;
754
+ } else {
755
+ player.velocityX = 5;
756
+ }
757
+ }
758
+ // Otherwise, player takes damage
759
+ else if (!player.invincible) {
760
+ player.health -= 10;
761
+ player.invincible = true;
762
+ player.invincibleTimer = 1;
763
+
764
+ // Knockback
765
+ if (player.x < enemy.x) {
766
+ player.velocityX = -8;
767
+ } else {
768
+ player.velocityX = 8;
769
+ }
770
+ player.velocityY = -5;
771
+
772
+ // Blood effect
773
+ createBloodParticles(player.x + player.width/2, player.y + player.height/2, 10);
774
+ }
775
+ }
776
+ }
777
+ }
778
+
779
+ // Check for trap collisions
780
+ function checkTrapCollisions() {
781
+ const { player, traps } = gameState;
782
+
783
+ for (const trap of traps) {
784
+ if (trap.type === 'spikes' || trap.type === 'blade') {
785
+ if (player.x < trap.x + trap.width &&
786
+ player.x + player.width > trap.x &&
787
+ player.y < trap.y + trap.height &&
788
+ player.y + player.height > trap.y) {
789
+
790
+ if (!player.invincible) {
791
+ player.health -= 20;
792
+ player.invincible = true;
793
+ player.invincibleTimer = 1;
794
+
795
+ // Knockback
796
+ player.velocityY = -10;
797
+ createBloodParticles(player.x + player.width/2, player.y + player.height/2, 15);
798
+ }
799
+ }
800
+ } else if (trap.type === 'pressurePlate' && !trap.triggered) {
801
+ if (player.x < trap.x + trap.width &&
802
+ player.x + player.width > trap.x &&
803
+ player.y < trap.y + trap.height &&
804
+ player.y + player.height > trap.y) {
805
+
806
+ trap.triggered = true;
807
+ // In a full game, this would trigger something (like opening a door)
808
+ createSparkEffect(trap.x + trap.width/2, trap.y);
809
+ }
810
+ }
811
+ }
812
+ }
813
+
814
+ // Check for ledge grab
815
+ function checkLedgeGrab() {
816
+ const { player, platforms } = gameState;
817
+
818
+ // Only check for ledge grab if player is falling and near a platform edge
819
+ if (player.velocityY > 0 && player.isJumping) {
820
+ for (const platform of platforms) {
821
+ // Skip if platform is collapsing and has collapsed
822
+ if (platform.collapsing && platform.collapseTimer >= 1) continue;
823
+
824
+ // Check if player is near the edge of a platform
825
+ const ledgeLeft = platform.x - player.width;
826
+ const ledgeRight = platform.x + platform.width;
827
+
828
+ // Check left edge
829
+ if (player.x + player.width/2 > ledgeLeft - 10 &&
830
+ player.x + player.width/2 < ledgeLeft + 10 &&
831
+ player.y + player.height < platform.y - 5 &&
832
+ player.y + player.height + player.velocityY >= platform.y - 5) {
833
+
834
+ // Grab the ledge
835
+ if (gameState.keys.down) {
836
+ // Player chooses to drop
837
+ return;
838
+ } else {
839
+ player.y = platform.y - player.height;
840
+ player.velocityY = 0;
841
+ player.isJumping = false;
842
+
843
+ // If player presses up, climb onto platform
844
+ if (gameState.keys.up) {
845
+ player.y = platform.y - player.height;
846
+ } else {
847
+ // Otherwise, hang from the ledge
848
+ player.x = ledgeLeft;
849
+ player.y = platform.y - player.height + 10;
850
+ }
851
+
852
+ return;
853
+ }
854
+ }
855
+
856
+ // Check right edge
857
+ if (player.x + player.width/2 > ledgeRight - 10 &&
858
+ player.x + player.width/2 < ledgeRight + 10 &&
859
+ player.y + player.height < platform.y - 5 &&
860
+ player.y + player.height + player.velocityY >= platform.y - 5) {
861
+
862
+ // Grab the ledge
863
+ if (gameState.keys.down) {
864
+ // Player chooses to drop
865
+ return;
866
+ } else {
867
+ player.y = platform.y - player.height;
868
+ player.velocityY = 0;
869
+ player.isJumping = false;
870
+
871
+ // If player presses up, climb onto platform
872
+ if (gameState.keys.up) {
873
+ player.y = platform.y - player.height;
874
+ } else {
875
+ // Otherwise, hang from the ledge
876
+ player.x = ledgeRight - player.width;
877
+ player.y = platform.y - player.height + 10;
878
+ }
879
+
880
+ return;
881
+ }
882
+ }
883
+ }
884
+ }
885
+ }
886
+
887
+ // Check if player's attack hits an enemy
888
+ function checkAttackHit() {
889
+ const { player, enemies } = gameState;
890
+
891
+ for (const enemy of enemies) {
892
+ if (enemy.health <= 0) continue;
893
+
894
+ // Check if enemy is in front of player and within attack range
895
+ if ((player.facingRight && enemy.x > player.x && enemy.x < player.x + 100) ||
896
+ (!player.facingRight && enemy.x < player.x && enemy.x > player.x - 100)) {
897
+
898
+ // Check vertical overlap
899
+ if (player.y + player.height > enemy.y &&
900
+ player.y < enemy.y + enemy.height) {
901
+
902
+ // Enemy takes damage
903
+ enemy.health -= 25;
904
+
905
+ // Create blood effect
906
+ createBloodParticles(enemy.x + enemy.width/2, enemy.y + enemy.height/2, 10);
907
+
908
+ // Knockback
909
+ if (player.facingRight) {
910
+ enemy.x += 20;
911
+ } else {
912
+ enemy.x -= 20;
913
+ }
914
+
915
+ // If enemy is defeated
916
+ if (enemy.health <= 0) {
917
+ // Create more blood
918
+ createBloodParticles(enemy.x + enemy.width/2, enemy.y + enemy.height/2, 20);
919
+
920
+ // Check if this was the elite guard (final enemy)
921
+ if (enemy.type === 'elite') {
922
+ // Create victory particles
923
+ createVictoryParticles(enemy.x + enemy.width/2, enemy.y + enemy.height/2);
924
+ }
925
+ }
926
+ }
927
+ }
928
+ }
929
+ }
930
+
931
+ // Update enemies
932
+ function updateEnemies() {
933
+ const { enemies, player } = gameState;
934
+
935
+ for (const enemy of enemies) {
936
+ if (enemy.health <= 0) continue;
937
+
938
+ // Simple AI: move toward player if nearby
939
+ const distanceToPlayer = Math.abs(player.x - enemy.x);
940
+
941
+ if (distanceToPlayer < 300) {
942
+ if (player.x < enemy.x) {
943
+ enemy.direction = -1;
944
+ } else {
945
+ enemy.direction = 1;
946
+ }
947
+
948
+ // Move enemy
949
+ enemy.x += enemy.speed * enemy.direction;
950
+
951
+ // Simple platform following
952
+ let onPlatform = false;
953
+ for (const platform of gameState.platforms) {
954
+ // Skip if platform is collapsing and has collapsed
955
+ if (platform.collapsing && platform.collapseTimer >= 1) continue;
956
+
957
+ if (enemy.x >= platform.x &&
958
+ enemy.x <= platform.x + platform.width &&
959
+ enemy.y + enemy.height >= platform.y - 5 &&
960
+ enemy.y + enemy.height <= platform.y + 5) {
961
+
962
+ onPlatform = true;
963
+ enemy.y = platform.y - enemy.height;
964
+
965
+ // Check if enemy is at edge of platform
966
+ if ((enemy.direction === -1 && enemy.x <= platform.x) ||
967
+ (enemy.direction === 1 && enemy.x + enemy.width >= platform.x + platform.width)) {
968
+ enemy.direction *= -1;
969
+ }
970
+
971
+ break;
972
+ }
973
+ }
974
+
975
+ // Apply gravity if not on platform
976
+ if (!onPlatform) {
977
+ enemy.y += 5;
978
+ }
979
+
980
+ // Attack if close enough
981
+ if (distanceToPlayer < 60 && enemy.attackCooldown <= 0) {
982
+ enemy.isAttacking = true;
983
+ enemy.attackCooldown = 1.5;
984
+
985
+ // Check if attack hits player
986
+ if (Math.abs(player.y - enemy.y) < 30 && !player.invincible) {
987
+ player.health -= 10;
988
+ player.invincible = true;
989
+ player.invincibleTimer = 1;
990
+
991
+ // Knockback
992
+ if (player.x < enemy.x) {
993
+ player.velocityX = -8;
994
+ } else {
995
+ player.velocityX = 8;
996
+ }
997
+ player.velocityY = -5;
998
+
999
+ // Blood effect
1000
+ createBloodParticles(player.x + player.width/2, player.y + player.height/2, 10);
1001
+ }
1002
+
1003
+ setTimeout(() => {
1004
+ enemy.isAttacking = false;
1005
+ }, 300);
1006
+ }
1007
+ }
1008
+
1009
+ // Update attack cooldown
1010
+ if (enemy.attackCooldown > 0) {
1011
+ enemy.attackCooldown -= gameState.deltaTime;
1012
+ }
1013
+ }
1014
+ }
1015
+
1016
+ // Update traps
1017
+ function updateTraps() {
1018
+ const { traps } = gameState;
1019
+
1020
+ for (const trap of traps) {
1021
+ if (trap.type === 'blade') {
1022
+ // Move blade back and forth
1023
+ trap.x += trap.speed * trap.direction;
1024
+
1025
+ // Reverse direction at bounds
1026
+ if (trap.x < 1100 || trap.x > 1300) {
1027
+ trap.direction *= -1;
1028
+ }
1029
+ } else if (trap.type === 'pressurePlate' && trap.triggered) {
1030
+ // In a full game, this would trigger something (like opening a door)
1031
+ }
1032
+ }
1033
+ }
1034
+
1035
+ // Update platforms
1036
+ function updatePlatforms() {
1037
+ const { platforms } = gameState;
1038
+
1039
+ for (const platform of platforms) {
1040
+ if (platform.collapsing) {
1041
+ // Check if player is on the platform
1042
+ const { player } = gameState;
1043
+ if (player.x >= platform.x &&
1044
+ player.x <= platform.x + platform.width &&
1045
+ player.y + player.height >= platform.y - 5 &&
1046
+ player.y + player.height <= platform.y + 5) {
1047
+
1048
+ // Start collapse timer
1049
+ platform.collapseTimer += gameState.deltaTime;
1050
+
1051
+ // Create shaking effect
1052
+ if (platform.collapseTimer < 1) {
1053
+ platform.x += Math.sin(platform.collapseTimer * 30) * 2;
1054
+ }
1055
+
1056
+ // Platform disappears after timer reaches 1
1057
+ if (platform.collapseTimer >= 1) {
1058
+ // Create falling debris particles
1059
+ createDebrisParticles(platform.x + platform.width/2, platform.y + platform.height/2, 20);
1060
+ }
1061
+ }
1062
+ }
1063
+ }
1064
+ }
1065
+
1066
+ // Update torches
1067
+ function updateTorches() {
1068
+ const { torches } = gameState;
1069
+
1070
+ for (const torch of torches) {
1071
+ torch.flicker = Math.random();
1072
+ }
1073
+ }
1074
+
1075
+ // Update particles
1076
+ function updateParticles() {
1077
+ const { particles } = gameState;
1078
+
1079
+ for (let i = particles.length - 1; i >= 0; i--) {
1080
+ const p = particles[i];
1081
+ p.x += p.velocityX;
1082
+ p.y += p.velocityY;
1083
+ p.life -= gameState.deltaTime;
1084
+
1085
+ if (p.life <= 0) {
1086
+ particles.splice(i, 1);
1087
+ }
1088
+ }
1089
+ }
1090
+
1091
+ // Update camera
1092
+ function updateCamera() {
1093
+ const { camera, player } = gameState;
1094
+
1095
+ // Camera follows player with some delay
1096
+ camera.targetX = player.x - canvas.width / 3;
1097
+ camera.targetY = player.y - canvas.height / 2;
1098
+
1099
+ // Smooth camera movement
1100
+ camera.x += (camera.targetX - camera.x) * 0.1;
1101
+ camera.y += (camera.targetY - camera.y) * 0.1;
1102
+
1103
+ // Keep camera within level bounds
1104
+ camera.x = Math.max(0, Math.min(camera.x, gameState.levelBounds.right - canvas.width));
1105
+ camera.y = Math.max(0, Math.min(camera.y, gameState.levelBounds.bottom - canvas.height));
1106
+
1107
+ // Zoom in slightly when attacking
1108
+ if (player.isAttacking) {
1109
+ camera.zoom = 1.1;
1110
+ } else {
1111
+ camera.zoom = 1;
1112
+ }
1113
+ }
1114
+
1115
+ // Create dust particles
1116
+ function createDustParticles(x, y, count) {
1117
+ for (let i = 0; i < count; i++) {
1118
+ gameState.particles.push({
1119
+ x: x,
1120
+ y: y,
1121
+ velocityX: (Math.random() - 0.5) * 2,
1122
+ velocityY: -Math.random() * 2,
1123
+ size: Math.random() * 3 + 1,
1124
+ color: '#d4a76a',
1125
+ life: Math.random() * 0.5 + 0.5,
1126
+ type: 'dust'
1127
+ });
1128
+ }
1129
+ }
1130
+
1131
+ // Create blood particles
1132
+ function createBloodParticles(x, y, count) {
1133
+ for (let i = 0; i < count; i++) {
1134
+ gameState.particles.push({
1135
+ x: x,
1136
+ y: y,
1137
+ velocityX: (Math.random() - 0.5) * 10,
1138
+ velocityY: (Math.random() - 0.5) * 10,
1139
+ size: Math.random() * 4 + 2,
1140
+ color: '#c41e3a',
1141
+ life: Math.random() * 1 + 0.5,
1142
+ type: 'blood'
1143
+ });
1144
+ }
1145
+ }
1146
+
1147
+ // Create spark effect
1148
+ function createSparkEffect(x, y) {
1149
+ for (let i = 0; i < 10; i++) {
1150
+ gameState.particles.push({
1151
+ x: x,
1152
+ y: y,
1153
+ velocityX: (Math.random() - 0.5) * 10,
1154
+ velocityY: (Math.random() - 0.5) * 10,
1155
+ size: Math.random() * 2 + 1,
1156
+ color: '#ffffff',
1157
+ life: Math.random() * 0.3 + 0.2,
1158
+ type: 'spark'
1159
+ });
1160
+ }
1161
+ }
1162
+
1163
+ // Create debris particles
1164
+ function createDebrisParticles(x, y, count) {
1165
+ for (let i = 0; i < count; i++) {
1166
+ gameState.particles.push({
1167
+ x: x,
1168
+ y: y,
1169
+ velocityX: (Math.random() - 0.5) * 5,
1170
+ velocityY: -Math.random() * 5,
1171
+ size: Math.random() * 6 + 3,
1172
+ color: '#8b4513',
1173
+ life: Math.random() * 1 + 0.5,
1174
+ type: 'debris'
1175
+ });
1176
+ }
1177
+ }
1178
+
1179
+ // Create victory particles
1180
+ function createVictoryParticles(x, y) {
1181
+ for (let i = 0; i < 30; i++) {
1182
+ gameState.particles.push({
1183
+ x: x,
1184
+ y: y,
1185
+ velocityX: (Math.random() - 0.5) * 10,
1186
+ velocityY: (Math.random() - 0.5) * 10,
1187
+ size: Math.random() * 4 + 2,
1188
+ color: ['#ffd700', '#ffffff', '#d4a76a'][Math.floor(Math.random() * 3)],
1189
+ life: Math.random() * 1.5 + 0.5,
1190
+ type: 'victory'
1191
+ });
1192
+ }
1193
+ }
1194
+
1195
+ // Create sword slash effect
1196
+ function createSwordSlash() {
1197
+ const { player } = gameState;
1198
+
1199
+ gameState.particles.push({
1200
+ x: player.facingRight ? player.x + player.width : player.x,
1201
+ y: player.y + player.height/2,
1202
+ velocityX: 0,
1203
+ velocityY: 0,
1204
+ size: 30,
1205
+ color: '#ffffff',
1206
+ life: 0.2,
1207
+ type: 'swordSlash',
1208
+ facingRight: player.facingRight
1209
+ });
1210
+ }
1211
+
1212
+ // Render game
1213
+ function render() {
1214
+ const { camera, player, platforms, enemies, traps, torches, particles } = gameState;
1215
+
1216
+ // Clear canvas
1217
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1218
+
1219
+ // Save context
1220
+ ctx.save();
1221
+
1222
+ // Apply camera transform
1223
+ ctx.translate(-camera.x, -camera.y);
1224
+ ctx.scale(camera.zoom, camera.zoom);
1225
+
1226
+ // Draw background
1227
+ drawBackground();
1228
+
1229
+ // Draw platforms
1230
+ drawPlatforms();
1231
+
1232
+ // Draw traps
1233
+ drawTraps();
1234
+
1235
+ // Draw torches
1236
+ drawTorches();
1237
+
1238
+ // Draw enemies
1239
+ drawEnemies();
1240
+
1241
+ // Draw player
1242
+ drawPlayer();
1243
+
1244
+ // Draw particles
1245
+ drawParticles();
1246
+
1247
+ // Draw exit door
1248
+ drawExit();
1249
+
1250
+ // Restore context
1251
+ ctx.restore();
1252
+
1253
+ // Draw UI elements (not affected by camera)
1254
+ drawUI();
1255
+ }
1256
+
1257
+ // Draw background
1258
+ function drawBackground() {
1259
+ const { camera } = gameState;
1260
+
1261
+ // Draw sky gradient
1262
+ const skyGradient = ctx.createLinearGradient(0, 0, 0, GAME_HEIGHT);
1263
+ skyGradient.addColorStop(0, '#1a1a2e');
1264
+ skyGradient.addColorStop(1, '#16213e');
1265
+ ctx.fillStyle = skyGradient;
1266
+ ctx.fillRect(0, 0, gameState.levelBounds.right, GAME_HEIGHT);
1267
+
1268
+ // Draw distant mountains
1269
+ ctx.fillStyle = '#0f3460';
1270
+ for (let i = 0; i < 5; i++) {
1271
+ const x = i * 600 - (camera.x * 0.2) % 600;
1272
+ const height = 150 + Math.sin(i) * 50;
1273
+ ctx.beginPath();
1274
+ ctx.moveTo(x, GAME_HEIGHT);
1275
+ ctx.lineTo(x + 300, GAME_HEIGHT - height);
1276
+ ctx.lineTo(x + 600, GAME_HEIGHT);
1277
+ ctx.closePath();
1278
+ ctx.fill();
1279
+ }
1280
+
1281
+ // Draw palace walls (parallax background)
1282
+ ctx.fillStyle = '#3d3d3d';
1283
+ for (let i = 0; i < 10; i++) {
1284
+ const x = i * 400 - (camera.x * 0.5) % 400;
1285
+ ctx.fillRect(x, GAME_HEIGHT - 300, 200, 300);
1286
+
1287
+ // Draw window details
1288
+ ctx.fillStyle = '#1a1a1a';
1289
+ ctx.fillRect(x + 50, GAME_HEIGHT - 250, 100, 150);
1290
+ ctx.fillStyle = '#3d3d3d';
1291
+
1292
+ // Draw pillars
1293
+ ctx.fillRect(x + 190, GAME_HEIGHT - 400, 20, 400);
1294
+ }
1295
+ }
1296
+
1297
+ // Draw platforms
1298
+ function drawPlatforms() {
1299
+ const { platforms } = gameState;
1300
+
1301
+ for (const platform of platforms) {
1302
+ // Skip if platform is collapsing and has collapsed
1303
+ if (platform.collapsing && platform.collapseTimer >= 1) continue;
1304
+
1305
+ // Draw platform base
1306
+ ctx.fillStyle = '#8b4513';
1307
+ ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
1308
+
1309
+ // Draw platform details
1310
+ ctx.fillStyle = '#6b3100';
1311
+ for (let i = 0; i < platform.width; i += 40) {
1312
+ ctx.fillRect(platform.x + i, platform.y, 40, 5);
1313
+ }
1314
+
1315
+ // Draw cracks if platform is collapsing
1316
+ if (platform.collapsing && platform.collapseTimer > 0) {
1317
+ ctx.strokeStyle = '#ffffff';
1318
+ ctx.lineWidth = 2;
1319
+
1320
+ const crackCount = Math.floor(platform.collapseTimer * 5);
1321
+ for (let i = 0; i < crackCount; i++) {
1322
+ const startX = platform.x + Math.random() * platform.width;
1323
+ const startY = platform.y + Math.random() * platform.height;
1324
+ const length = 10 + Math.random() * 20;
1325
+ const angle = Math.random() * Math.PI;
1326
+
1327
+ ctx.beginPath();
1328
+ ctx.moveTo(startX, startY);
1329
+ ctx.lineTo(startX + Math.cos(angle) * length, startY + Math.sin(angle) * length);
1330
+ ctx.stroke();
1331
+ }
1332
+ }
1333
+ }
1334
+ }
1335
+
1336
+ // Draw traps
1337
+ function drawTraps() {
1338
+ const { traps } = gameState;
1339
+
1340
+ for (const trap of traps) {
1341
+ if (trap.type === 'spikes') {
1342
+ // Draw spike pit
1343
+ ctx.fillStyle = '#333333';
1344
+ ctx.fillRect(trap.x, trap.y, trap.width, trap.height);
1345
+
1346
+ // Draw spikes
1347
+ ctx.fillStyle = '#666666';
1348
+ const spikeCount = Math.floor(trap.width / 10);
1349
+ for (let i = 0; i < spikeCount; i++) {
1350
+ ctx.beginPath();
1351
+ ctx.moveTo(trap.x + i * 10 + 5, trap.y + trap.height);
1352
+ ctx.lineTo(trap.x + i * 10, trap.y);
1353
+ ctx.lineTo(trap.x + i * 10 + 10, trap.y);
1354
+ ctx.closePath();
1355
+ ctx.fill();
1356
+ }
1357
+ } else if (trap.type === 'blade') {
1358
+ // Draw rolling blade
1359
+ ctx.fillStyle = '#999999';
1360
+ ctx.beginPath();
1361
+ ctx.arc(trap.x + trap.width/2, trap.y + trap.height/2, trap.width/2, 0, Math.PI * 2);
1362
+ ctx.fill();
1363
+
1364
+ // Draw blade edges
1365
+ ctx.strokeStyle = '#333333';
1366
+ ctx.lineWidth = 3;
1367
+ ctx.beginPath();
1368
+ ctx.arc(trap.x + trap.width/2, trap.y + trap.height/2, trap.width/2 - 5, 0, Math.PI * 2);
1369
+ ctx.stroke();
1370
+
1371
+ // Draw spikes on blade
1372
+ ctx.fillStyle = '#ff0000';
1373
+ for (let i = 0; i < 8; i++) {
1374
+ const angle = (i / 8) * Math.PI * 2;
1375
+ const spikeX = trap.x + trap.width/2 + Math.cos(angle) * (trap.width/2 - 5);
1376
+ const spikeY = trap.y + trap.height/2 + Math.sin(angle) * (trap.height/2 - 5);
1377
+
1378
+ ctx.beginPath();
1379
+ ctx.moveTo(spikeX, spikeY);
1380
+ ctx.lineTo(
1381
+ spikeX + Math.cos(angle) * 10,
1382
+ spikeY + Math.sin(angle) * 10
1383
+ );
1384
+ ctx.lineWidth = 3;
1385
+ ctx.stroke();
1386
+ }
1387
+ } else if (trap.type === 'pressurePlate') {
1388
+ // Draw pressure plate
1389
+ ctx.fillStyle = trap.triggered ? '#555555' : '#999999';
1390
+ ctx.fillRect(trap.x, trap.y, trap.width, trap.height);
1391
+
1392
+ // Draw plate details
1393
+ ctx.strokeStyle = '#333333';
1394
+ ctx.lineWidth = 2;
1395
+ ctx.strokeRect(trap.x + 2, trap.y + 2, trap.width - 4, trap.height - 4);
1396
+ }
1397
+ }
1398
+ }
1399
+
1400
+ // Draw torches
1401
+ function drawTorches() {
1402
+ const { torches } = gameState;
1403
+
1404
+ for (const torch of torches) {
1405
+ // Draw torch holder
1406
+ ctx.fillStyle = '#8b4513';
1407
+ ctx.fillRect(torch.x - 5, torch.y - 40, 10, 40);
1408
+
1409
+ // Draw torch base
1410
+ ctx.fillStyle = '#6b3100';
1411
+ ctx.beginPath();
1412
+ ctx.arc(torch.x, torch.y - 10, 10, 0, Math.PI * 2);
1413
+ ctx.fill();
1414
+
1415
+ // Draw flame
1416
+ if (torch.lit) {
1417
+ const opacity = 0.6 + torch.flicker * 0.4;
1418
+ const flameGradient = ctx.createRadialGradient(
1419
+ torch.x, torch.y - 25, 5,
1420
+ torch.x, torch.y - 25, 15
1421
+ );
1422
+ flameGradient.addColorStop(0, `rgba(255, 200, 0, ${opacity})`);
1423
+ flameGradient.addColorStop(0.5, `rgba(255, 100, 0, ${opacity * 0.7})`);
1424
+ flameGradient.addColorStop(1, `rgba(255, 50, 0, 0)`);
1425
+
1426
+ ctx.fillStyle = flameGradient;
1427
+ ctx.beginPath();
1428
+ ctx.moveTo(torch.x - 10, torch.y - 10);
1429
+ ctx.quadraticCurveTo(torch.x, torch.y - 50, torch.x + 10, torch.y - 10);
1430
+ ctx.fill();
1431
+
1432
+ // Draw light cast by torch
1433
+ const lightGradient = ctx.createRadialGradient(
1434
+ torch.x, torch.y - 15, 0,
1435
+ torch.x, torch.y - 15, 100
1436
+ );
1437
+ lightGradient.addColorStop(0, `rgba(255, 200, 100, ${0.1 + torch.flicker * 0.05})`);
1438
+ lightGradient.addColorStop(1, `rgba(0, 0, 0, 0)`);
1439
+
1440
+ ctx.fillStyle = lightGradient;
1441
+ ctx.fillRect(torch.x - 100, torch.y - 115, 200, 200);
1442
+ }
1443
+ }
1444
+ }
1445
+
1446
+ // Draw enemies
1447
+ function drawEnemies() {
1448
+ const { enemies } = gameState;
1449
+
1450
+ for (const enemy of enemies) {
1451
+ if (enemy.health <= 0) continue;
1452
+
1453
+ // Draw enemy body
1454
+ ctx.fillStyle = enemy.type === 'elite' ? '#4a4a4a' : '#555555';
1455
+ ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
1456
+
1457
+ // Draw enemy head
1458
+ ctx.fillStyle = enemy.type === 'elite' ? '#d4a76a' : '#8b4513';
1459
+ ctx.beginPath();
1460
+ ctx.arc(
1461
+ enemy.x + enemy.width/2,
1462
+ enemy.y - 10,
1463
+ 15,
1464
+ 0,
1465
+ Math.PI * 2
1466
+ );
1467
+ ctx.fill();
1468
+
1469
+ // Draw enemy eyes
1470
+ ctx.fillStyle = '#ffffff';
1471
+ ctx.beginPath();
1472
+ ctx.arc(
1473
+ enemy.x + enemy.width/2 - 5,
1474
+ enemy.y - 15,
1475
+ 3,
1476
+ 0,
1477
+ Math.PI * 2
1478
+ );
1479
+ ctx.arc(
1480
+ enemy.x + enemy.width/2 + 5,
1481
+ enemy.y - 15,
1482
+ 3,
1483
+ 0,
1484
+ Math.PI * 2
1485
+ );
1486
+ ctx.fill();
1487
+
1488
+ // Draw enemy weapon
1489
+ ctx.strokeStyle = '#999999';
1490
+ ctx.lineWidth = 3;
1491
+ ctx.beginPath();
1492
+ if (enemy.isAttacking) {
1493
+ // Draw sword in attack position
1494
+ ctx.moveTo(
1495
+ enemy.x + (enemy.direction === 1 ? enemy.width : 0),
1496
+ enemy.y + 20
1497
+ );
1498
+ ctx.lineTo(
1499
+ enemy.x + (enemy.direction === 1 ? enemy.width + 30 : -30),
1500
+ enemy.y + 40
1501
+ );
1502
+ } else {
1503
+ // Draw sword in ready position
1504
+ ctx.moveTo(
1505
+ enemy.x + (enemy.direction === 1 ? enemy.width : 0),
1506
+ enemy.y + 20
1507
+ );
1508
+ ctx.lineTo(
1509
+ enemy.x + (enemy.direction === 1 ? enemy.width + 10 : -10),
1510
+ enemy.y + 40
1511
+ );
1512
+ }
1513
+ ctx.stroke();
1514
+
1515
+ // Draw enemy health bar if damaged
1516
+ if (enemy.health < (enemy.type === 'elite' ? 100 : 50)) {
1517
+ const healthWidth = 40;
1518
+ const healthPercent = enemy.health / (enemy.type === 'elite' ? 100 : 50);
1519
+
1520
+ ctx.fillStyle = '#333333';
1521
+ ctx.fillRect(
1522
+ enemy.x + enemy.width/2 - healthWidth/2,
1523
+ enemy.y - 30,
1524
+ healthWidth,
1525
+ 5
1526
+ );
1527
+
1528
+ ctx.fillStyle = healthPercent > 0.5 ? '#00ff00' : healthPercent > 0.25 ? '#ffff00' : '#ff0000';
1529
+ ctx.fillRect(
1530
+ enemy.x + enemy.width/2 - healthWidth/2,
1531
+ enemy.y - 30,
1532
+ healthWidth * healthPercent,
1533
+ 5
1534
+ );
1535
+ }
1536
+ }
1537
+ }
1538
+
1539
+ // Draw player
1540
+ function drawPlayer() {
1541
+ const { player } = gameState;
1542
+
1543
+ // Draw player body
1544
+ ctx.fillStyle = player.invincible && Math.floor(Date.now() / 100) % 2 === 0 ?
1545
+ 'rgba(212, 167, 106, 0.5)' : '#d4a76a';
1546
+ ctx.fillRect(player.x, player.y, player.width, player.height);
1547
+
1548
+ // Draw player head
1549
+ ctx.fillStyle = '#8b4513';
1550
+ ctx.beginPath();
1551
+ ctx.arc(
1552
+ player.x + player.width/2,
1553
+ player.y - 10,
1554
+ 15,
1555
+ 0,
1556
+ Math.PI * 2
1557
+ );
1558
+ ctx.fill();
1559
+
1560
+ // Draw player eyes
1561
+ ctx.fillStyle = '#ffffff';
1562
+ ctx.beginPath();
1563
+ ctx.arc(
1564
+ player.x + player.width/2 - (player.facingRight ? 5 : 8),
1565
+ player.y - 15,
1566
+ 3,
1567
+ 0,
1568
+ Math.PI * 2
1569
+ );
1570
+ ctx.arc(
1571
+ player.x + player.width/2 + (player.facingRight ? 8 : 5),
1572
+ player.y - 15,
1573
+ 3,
1574
+ 0,
1575
+ Math.PI * 2
1576
+ );
1577
+ ctx.fill();
1578
+
1579
+ // Draw player weapon if attacking
1580
+ if (player.isAttacking) {
1581
+ ctx.strokeStyle = '#ffffff';
1582
+ ctx.lineWidth = 4;
1583
+ ctx.beginPath();
1584
+ ctx.moveTo(
1585
+ player.x + (player.facingRight ? player.width : 0),
1586
+ player.y + 20
1587
+ );
1588
+ ctx.lineTo(
1589
+ player.x + (player.facingRight ? player.width + 40 : -40),
1590
+ player.y + 30
1591
+ );
1592
+ ctx.stroke();
1593
+ }
1594
+
1595
+ // Draw player cape
1596
+ ctx.fillStyle = player.invincible && Math.floor(Date.now() / 100) % 2 === 0 ?
1597
+ 'rgba(139, 0, 0, 0.5)' : '#8b0000';
1598
+ ctx.beginPath();
1599
+ ctx.moveTo(player.x, player.y + 10);
1600
+ ctx.quadraticCurveTo(
1601
+ player.x - 15,
1602
+ player.y + player.height/2,
1603
+ player.x,
1604
+ player.y + player.height
1605
+ );
1606
+ ctx.quadraticCurveTo(
1607
+ player.x + 10,
1608
+ player.y + player.height/2,
1609
+ player.x,
1610
+ player.y + 10
1611
+ );
1612
+ ctx.fill();
1613
+
1614
+ // Draw player shadow
1615
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
1616
+ ctx.beginPath();
1617
+ ctx.ellipse(
1618
+ player.x + player.width/2,
1619
+ player.y + player.height + 5,
1620
+ player.width/2,
1621
+ 5,
1622
+ 0,
1623
+ 0,
1624
+ Math.PI * 2
1625
+ );
1626
+ ctx.fill();
1627
+ }
1628
+
1629
+ // Draw particles
1630
+ function drawParticles() {
1631
+ const { particles } = gameState;
1632
+
1633
+ for (const p of particles) {
1634
+ if (p.type === 'dust') {
1635
+ ctx.fillStyle = p.color;
1636
+ ctx.beginPath();
1637
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
1638
+ ctx.fill();
1639
+ } else if (p.type === 'blood') {
1640
+ ctx.fillStyle = p.color;
1641
+ ctx.beginPath();
1642
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
1643
+ ctx.fill();
1644
+ } else if (p.type === 'spark') {
1645
+ ctx.fillStyle = p.color;
1646
+ ctx.beginPath();
1647
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
1648
+ ctx.fill();
1649
+ } else if (p.type === 'debris') {
1650
+ ctx.fillStyle = p.color;
1651
+ ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
1652
+ } else if (p.type === 'victory') {
1653
+ ctx.fillStyle = p.color;
1654
+ ctx.beginPath();
1655
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
1656
+ ctx.fill();
1657
+ } else if (p.type === 'swordSlash') {
1658
+ const gradient = ctx.createLinearGradient(
1659
+ p.x,
1660
+ p.y - 20,
1661
+ p.x,
1662
+ p.y + 20
1663
+ );
1664
+ gradient.addColorStop(0, 'rgba(255, 255, 255, 0.8)');
1665
+ gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
1666
+
1667
+ ctx.fillStyle = gradient;
1668
+ ctx.beginPath();
1669
+ if (p.facingRight) {
1670
+ ctx.moveTo(p.x, p.y - 20);
1671
+ ctx.lineTo(p.x + 40, p.y);
1672
+ ctx.lineTo(p.x, p.y + 20);
1673
+ } else {
1674
+ ctx.moveTo(p.x, p.y - 20);
1675
+ ctx.lineTo(p.x - 40, p.y);
1676
+ ctx.lineTo(p.x, p.y + 20);
1677
+ }
1678
+ ctx.closePath();
1679
+ ctx.fill();
1680
+ }
1681
+ }
1682
+ }
1683
+
1684
+ // Draw exit door
1685
+ function drawExit() {
1686
+ const { player } = gameState;
1687
+
1688
+ // Draw exit door
1689
+ ctx.fillStyle = '#6b3100';
1690
+ ctx.fillRect(2800, GAME_HEIGHT - 250, 50, 200);
1691
+
1692
+ // Draw door details
1693
+ ctx.fillStyle = '#8b4513';
1694
+ ctx.fillRect(2805, GAME_HEIGHT - 245, 40, 190);
1695
+
1696
+ // Draw door handle
1697
+ ctx.fillStyle = '#d4a76a';
1698
+ ctx.beginPath();
1699
+ ctx.arc(2830, GAME_HEIGHT - 150, 5, 0, Math.PI * 2);
1700
+ ctx.fill();
1701
+
1702
+ // Draw glowing effect if player is near
1703
+ if (player.x > 2700) {
1704
+ const gradient = ctx.createRadialGradient(
1705
+ 2825, GAME_HEIGHT - 150, 0,
1706
+ 2825, GAME_HEIGHT - 150, 100
1707
+ );
1708
+ gradient.addColorStop(0, 'rgba(255, 255, 255, 0.5)');
1709
+ gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
1710
+
1711
+ ctx.fillStyle = gradient;
1712
+ ctx.fillRect(2725, GAME_HEIGHT - 250, 200, 200);
1713
+ }
1714
+ }
1715
+
1716
+ // Draw UI
1717
+ function drawUI() {
1718
+ // In a full game, this would draw more UI elements
1719
+ // Currently handled by HTML/CSS
1720
+ }
1721
+
1722
+ // Initialize the game when the page loads
1723
+ window.addEventListener('load', initGame);
1724
+ </script>
1725
+ <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=LukasBe/dungeon-quest" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
1726
+ </html>