etnom commited on
Commit
c27f2e2
·
verified ·
1 Parent(s): 6043a06

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1668 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Gauntlet
3
- emoji: 👀
4
- colorFrom: green
5
- colorTo: red
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: gauntlet
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
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,1668 @@
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>Dungeon Adventure</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ #gameCanvas {
11
+ background-color: #222;
12
+ display: block;
13
+ margin: 0 auto;
14
+ border: 4px solid #8B4513;
15
+ box-shadow: 0 0 20px rgba(139, 69, 19, 0.5);
16
+ image-rendering: pixelated;
17
+ }
18
+ .health-bar {
19
+ height: 20px;
20
+ background-color: #333;
21
+ border-radius: 10px;
22
+ overflow: hidden;
23
+ position: relative;
24
+ }
25
+ .health-fill {
26
+ height: 100%;
27
+ background-color: #ff3333;
28
+ transition: width 0.3s;
29
+ }
30
+ .mana-bar {
31
+ height: 20px;
32
+ background-color: #333;
33
+ border-radius: 10px;
34
+ overflow: hidden;
35
+ position: relative;
36
+ }
37
+ .mana-fill {
38
+ height: 100%;
39
+ background-color: #3399ff;
40
+ transition: width 0.3s;
41
+ }
42
+ .game-container {
43
+ background-color: #111;
44
+ border-radius: 10px;
45
+ padding: 20px;
46
+ box-shadow: 0 0 30px rgba(0, 0, 0, 0.7);
47
+ }
48
+ .character-select {
49
+ transition: all 0.3s;
50
+ }
51
+ .character-select:hover {
52
+ transform: scale(1.05);
53
+ box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
54
+ }
55
+ .character-select.selected {
56
+ border-color: gold;
57
+ box-shadow: 0 0 15px rgba(255, 215, 0, 0.8);
58
+ }
59
+ .particle {
60
+ position: absolute;
61
+ border-radius: 50%;
62
+ pointer-events: none;
63
+ }
64
+ </style>
65
+ </head>
66
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 font-mono">
67
+ <div id="gameContainer" class="game-container max-w-4xl w-full transition-all duration-300">
68
+ <!-- Character Selection Screen -->
69
+ <div id="characterSelection" class="">
70
+ <h1 class="text-4xl font-bold text-center mb-6 text-yellow-400">
71
+ <i class="fas fa-dungeon mr-2"></i> Dungeon Adventure
72
+ </h1>
73
+
74
+ <div class="text-center mb-8">
75
+ <p class="mb-4">Choose your hero and descend into the dungeon!</p>
76
+ <p class="text-sm text-gray-400">Arrow keys to move, Space to attack</p>
77
+ </div>
78
+
79
+ <div class="grid grid-cols-1 sm:grid-cols-3 gap-6 mb-8">
80
+ <div class="character-select border-2 border-gray-700 rounded-lg pa4 p-4 cursor-pointer" data-class="warrior">
81
+ <div class="text-2xl text-center mb-2 text-red-400"><i class="fas fa-shield-alt"></i></div>
82
+ <h3 class="text-xl font-bold text-center mb-2">Warrior</h3>
83
+ <p class="text-sm text-gray-300 text-center">High health, strong attacks</p>
84
+ <div class="mt-2">
85
+ <div class="flex items-center text-sm">
86
+ <i class="fas fa-heart text-red-500 mr-2"></i>
87
+ <span>120 HP</span>
88
+ </div>
89
+ <div class="flex items-center text-sm">
90
+ <i class="fas fa-bolt text-blue-400 mr-2"></i>
91
+ <span>30 MP</span>
92
+ </div>
93
+ <div class="flex items-center text-sm">
94
+ <i class="fas fa-fist-raised text-yellow-400 mr-2"></i>
95
+ <span>Powerful strikes</span>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <div class="character-select border-2 border-gray-700 rounded-lg p-4 cursor-pointer" data-class="rogue">
101
+ <div class="text-2xl text-center mb-2 text-green-400"><i class="fas fa-user-ninja"></i></div>
102
+ <h3 class="text-xl font-bold text-center mb-2">Rogue</h3>
103
+ <p class="text-sm text-gray-300 text-center">Fast attacks, critical hits</p>
104
+ <div class="mt-2">
105
+ <div class="flex items-center text-sm">
106
+ <i class="fas fa-heart text-red-500 mr-2"></i>
107
+ <span>90 HP</span>
108
+ </div>
109
+ <div class="flex items-center text-sm">
110
+ <i class="fas fa-bolt text-blue-400 mr-2"></i>
111
+ <span>50 MP</span>
112
+ </div>
113
+ <div class="flex items-center text-sm">
114
+ <i class="fas fa-running text-green-400 mr-2"></i>
115
+ <span>Quick strikes</span>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <div class="character-select border-2 border-gray-700 rounded-lg p-4 cursor-pointer" data-class="mage">
121
+ <div class="text-2xl text-center mb-2 text-blue-400"><i class="fas fa-hat-wizard"></i></div>
122
+ <h3 class="text-xl font-bold text-center mb-2">Mage</h3>
123
+ <p class="text-sm text-gray-300 text-center">Ranged attacks, mana shield</p>
124
+ <div class="mt-2">
125
+ <div class="flex items-center text-sm">
126
+ <i class="fas fa-heart text-red-500 mr-2"></i>
127
+ <span>80 HP</span>
128
+ </div>
129
+ <div class="flex items-center text-sm">
130
+ <i class="fas fa-bolt text-blue-400 mr-2"></i>
131
+ <span>80 MP</span>
132
+ </div>
133
+ <div class="flex items-center text-sm">
134
+ <i class="fas fa-magic text-purple-400 mr-2"></i>
135
+ <span>Magic attacks</span>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+
141
+ <div class="text-center">
142
+ <button id="startGameBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-8 rounded-lg text-lg transition-all">
143
+ <i class="fas fa-play mr-2"></i> Begin Adventure
144
+ </button>
145
+ </div>
146
+ </div>
147
+
148
+ <!-- Game Screen -->
149
+ <div id="gameScreen" class="hidden">
150
+ <div class="flex justify-between items-center mb-4">
151
+ <div class="flex items-center space-x-4">
152
+ <div>
153
+ <div class="flex items-center mb-1">
154
+ <i class="fas fa-heart text-red-500 mr-2"></i>
155
+ <span id="healthText">100/100</span>
156
+ </div>
157
+ <div class="health-bar w-48">
158
+ <div id="healthBar" class="health-fill" style="width: 100%"></div>
159
+ </div>
160
+ </div>
161
+ <div>
162
+ <div class="flex items-center mb-1">
163
+ <i class="fas fa-bolt text-blue-400 mr-2"></i>
164
+ <span id="manaText">50/50</span>
165
+ </div>
166
+ <div class="mana-bar w-48">
167
+ <div id="manaBar" class="mana-fill" style="width: 100%"></div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ <div class="flex space-x-6">
173
+ <div class="text-xl font-bold">
174
+ <i class="fas fa-coins text-yellow-400 mr-2"></i>
175
+ <span id="goldCount">0</span>
176
+ </div>
177
+ <div class="text-xl font-bold">
178
+ <i class="fas fa-skull text-gray-400 mr-2"></i>
179
+ <span id="killCount">0</span>
180
+ </div>
181
+ <div class="text-xl font-bold">
182
+ <i class="fas fa-layer-group text-blue-200 mr-2"></i>
183
+ <span id="floorCount">1</span>
184
+ </div>
185
+ </div>
186
+ </div>
187
+
188
+ <canvas id="gameCanvas" width="800" height="500"></canvas>
189
+
190
+ <div class="mt-4 flex justify-between items-center">
191
+ <div class="text-sm text-gray-400">
192
+ Arrow keys to move, Space to attack
193
+ </div>
194
+ <div>
195
+ <button id="restartBtn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded mr-2">
196
+ <i class="fas fa-redo mr-2"></i> Restart
197
+ </button>
198
+ <button id="menuBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
199
+ <i class="fas fa-home mr-2"></i> Menu
200
+ </button>
201
+ </div>
202
+ </div>
203
+ </div>
204
+ </div>
205
+
206
+ <script>
207
+ // Game variables
208
+ let canvas, ctx;
209
+ let gameRunning = false;
210
+ let lastTime = 0;
211
+ let selectedCharacter = null;
212
+ let goldCount = 0;
213
+ let killCount = 0;
214
+ let floorCount = 1;
215
+ let dungeonMap = [];
216
+ let tileSize = 40;
217
+ let mapWidth = 20;
218
+ let mapHeight = 12;
219
+
220
+ const gameState = {
221
+ player: {
222
+ x: 2,
223
+ y: 2,
224
+ width: 30,
225
+ height: 30,
226
+ color: '#ff5555',
227
+ health: 100,
228
+ maxHealth: 100,
229
+ mana: 50,
230
+ maxMana: 50,
231
+ speed: 3,
232
+ attackPower: 20,
233
+ attackRange: 60,
234
+ attackCooldown: 0,
235
+ direction: 'right',
236
+ stats: {
237
+ strength: 1,
238
+ dexterity: 1,
239
+ intelligence: 1
240
+ },
241
+ class: ''
242
+ },
243
+ enemies: [],
244
+ treasures: [],
245
+ particles: [],
246
+ rooms: [],
247
+ staircases: {
248
+ up: null,
249
+ down: null
250
+ },
251
+ keys: {},
252
+ gameTime: 0
253
+ };
254
+
255
+ // Sprites
256
+ const sprites = {
257
+ player: {
258
+ warrior: {
259
+ right: { x: 0, y: 0, width: 32, height: 32 },
260
+ left: { x: 32, y: 0, width: 32, height: 32 },
261
+ up: { x: 64, y: 0, width: 32, height: 32 },
262
+ down: { x: 96, y: 0, width: 32, height: 32 }
263
+ },
264
+ rogue: {
265
+ right: { x: 0, y: 32, width: 32, height: 32 },
266
+ left: { x: 32, y: 32, width: 32, height: 32 },
267
+ up: { x: 64, y: 32, width: 32, height: 32 },
268
+ down: { x: 96, y: 32, width: 32, height: 32 }
269
+ },
270
+ mage: {
271
+ right: { x: 0, y: 64, width: 32, height: 32 },
272
+ left: { x: 32, y: 64, width: 32, height: 32 },
273
+ up: { x: 64, y: 64, width: 32, height: 32 },
274
+ down: { x: 96, y: 64, width: 32, height: 32 }
275
+ }
276
+ },
277
+ enemies: {
278
+ slime: { x: 0, y: 96, width: 32, height: 32 },
279
+ skeleton: { x: 32, y: 96, width: 32, height: 32 },
280
+ bat: { x: 64, y: 96, width: 32, height: 32 }
281
+ },
282
+ items: {
283
+ treasure: { x: 96, y: 96, width: 32, height: 32 },
284
+ healthPotion: { x: 128, y: 96, width: 32, height: 32 },
285
+ manaPotion: { x: 160, y: 96, width: 32, height: 32 },
286
+ stairsUp: { x: 0, y: 128, width: 32, height: 32 },
287
+ stairsDown: { x: 32, y: 128, width: 32, height: 32 }
288
+ },
289
+ tiles: {
290
+ floor: { x: 0, y: 0, width: 32, height: 32 },
291
+ wall: { x: 32, y: 0, width: 32, height: 32 },
292
+ corridor: { x: 64, y: 0, width: 32, height: 32 }
293
+ }
294
+ };
295
+
296
+ // Initialize game
297
+ document.addEventListener('DOMContentLoaded', () => {
298
+ // Character selection
299
+ const characterSelects = document.querySelectorAll('.character-select');
300
+ characterSelects.forEach(select => {
301
+ select.addEventListener('click', () => {
302
+ characterSelects.forEach(s => s.classList.remove('selected', 'border-yellow-400'));
303
+ select.classList.add('selected', 'border-yellow-400');
304
+ selectedCharacter = select.dataset.class;
305
+ });
306
+ });
307
+
308
+ // Start game button
309
+ document.getElementById('startGameBtn').addEventListener('click', () => {
310
+ if (!selectedCharacter) {
311
+ alert('Please select a character first!');
312
+ return;
313
+ }
314
+ startGame();
315
+ });
316
+
317
+ // Restart and menu buttons
318
+ document.getElementById('restartBtn').addEventListener('click', restartGame);
319
+ document.getElementById('menuBtn').addEventListener('click', returnToMenu);
320
+
321
+ // Initialize canvas
322
+ canvas = document.getElementById('gameCanvas');
323
+ ctx = canvas.getContext('2d');
324
+
325
+ // Keyboard controls
326
+ window.addEventListener('keydown', handleKeyDown);
327
+ window.addEventListener('keyup', handleKeyUp);
328
+ });
329
+
330
+ function handleKeyDown(e) {
331
+ if (!gameRunning) return;
332
+
333
+ // Prevent default for game controls
334
+ if ([32, 37, 38, 39, 40].includes(e.keyCode)) {
335
+ e.preventDefault();
336
+ }
337
+
338
+ gameState.keys[e.key.toLowerCase()] = true;
339
+
340
+ // Attack with space
341
+ if (e.key === ' ' && gameState.player.attackCooldown <= 0) {
342
+ attack();
343
+ }
344
+ }
345
+
346
+ function handleKeyUp(e) {
347
+ gameState.keys[e.key.toLowerCase()] = false;
348
+ }
349
+
350
+ // Start the game
351
+ function startGame() {
352
+ document.getElementById('characterSelection').classList.add('hidden');
353
+ document.getElementById('gameScreen').classList.remove('hidden');
354
+
355
+ // Set up player based on selected character
356
+ setupPlayer();
357
+
358
+ // Initialize dungeon
359
+ generateDungeon();
360
+
361
+ // Start game loop
362
+ gameRunning = true;
363
+ lastTime = performance.now();
364
+ requestAnimationFrame(gameLoop);
365
+ }
366
+
367
+ function setupPlayer() {
368
+ const player = gameState.player;
369
+ player.class = selectedCharacter;
370
+
371
+ // Set stats based on class
372
+ switch (selectedCharacter) {
373
+ case 'warrior':
374
+ player.maxHealth = 120;
375
+ player.health = 120;
376
+ player.maxMana = 30;
377
+ player.mana = 30;
378
+ player.speed = 3;
379
+ player.attackPower = 25;
380
+ player.stats.strength = 3;
381
+ player.stats.dexterity = 1;
382
+ player.stats.intelligence = 1;
383
+ break;
384
+ case 'rogue':
385
+ player.maxHealth = 90;
386
+ player.health = 90;
387
+ player.maxMana = 50;
388
+ player.mana = 50;
389
+ player.speed = 4;
390
+ player.attackPower = 18;
391
+ player.stats.strength = 1;
392
+ player.stats.dexterity = 3;
393
+ player.stats.intelligence = 1;
394
+ break;
395
+ case 'mage':
396
+ player.maxHealth = 80;
397
+ player.health = 80;
398
+ player.maxMana = 80;
399
+ player.mana = 80;
400
+ player.speed = 3;
401
+ player.attackPower = 15;
402
+ player.stats.strength = 1;
403
+ player.stats.dexterity = 1;
404
+ player.stats.intelligence = 3;
405
+ break;
406
+ }
407
+
408
+ // Update UI
409
+ updateUI();
410
+ }
411
+
412
+ function generateDungeon() {
413
+ dungeonMap = [];
414
+ gameState.enemies = [];
415
+ gameState.treasures = [];
416
+ gameState.rooms = [];
417
+
418
+ // Initialize empty map (0 = wall, 1 = floor)
419
+ for (let y = 0; y < mapHeight; y++) {
420
+ dungeonMap[y] = [];
421
+ for (let x = 0; x < mapWidth; x++) {
422
+ dungeonMap[y][x] = 0;
423
+ }
424
+ }
425
+
426
+ // Generate rooms
427
+ const roomCount = 3 + Math.floor(Math.random() * 3);
428
+ for (let i = 0; i < roomCount; i++) {
429
+ let roomPlaced = false;
430
+ let tries = 0;
431
+ const maxTries = 20;
432
+
433
+ while (!roomPlaced && tries < maxTries) {
434
+ tries++;
435
+
436
+ const width = 3 + Math.floor(Math.random() * 4);
437
+ const height = 3 + Math.floor(Math.random() * 4);
438
+ const x = 1 + Math.floor(Math.random() * (mapWidth - width - 1));
439
+ const y = 1 + Math.floor(Math.random() * (mapHeight - height - 1));
440
+
441
+ // Check if room overlaps with existing rooms
442
+ let canPlace = true;
443
+ for (let ry = y - 1; ry < y + height + 1; ry++) {
444
+ for (let rx = x - 1; rx < x + width + 1; rx++) {
445
+ if (dungeonMap[ry][rx] === 1) {
446
+ canPlace = false;
447
+ break;
448
+ }
449
+ }
450
+ if (!canPlace) break;
451
+ }
452
+
453
+ // Place the room
454
+ if (canPlace) {
455
+ for (let ry = y; ry < y + height; ry++) {
456
+ for (let rx = x; rx < x + width; rx++) {
457
+ dungeonMap[ry][rx] = 1;
458
+ }
459
+ }
460
+
461
+ gameState.rooms.push({
462
+ x, y, width, height,
463
+ centerX: x + Math.floor(width / 2),
464
+ centerY: y + Math.floor(height / 2)
465
+ });
466
+
467
+ roomPlaced = true;
468
+ }
469
+ }
470
+ }
471
+
472
+ // Connect rooms with corridors
473
+ for (let i = 0; i < gameState.rooms.length - 1; i++) {
474
+ const room1 = gameState.rooms[i];
475
+ const room2 = gameState.rooms[i + 1];
476
+
477
+ // 50% chance to connect horizontally first
478
+ if (Math.random() > 0.5) {
479
+ connectHorizontally(room1.centerX, room2.centerX, room1.centerY);
480
+ connectVertically(room1.centerY, room2.centerY, room2.centerX);
481
+ } else {
482
+ connectVertically(room1.centerY, room2.centerY, room1.centerX);
483
+ connectHorizontally(room1.centerX, room2.centerX, room2.centerY);
484
+ }
485
+ }
486
+
487
+ // Place player in first room
488
+ const startRoom = gameState.rooms[0];
489
+ gameState.player.x = startRoom.centerX;
490
+ gameState.player.y = startRoom.centerY;
491
+
492
+ // Place staircases
493
+ const endRoom = gameState.rooms[gameState.rooms.length - 1];
494
+ gameState.staircases.down = { x: endRoom.centerX, y: endRoom.centerY };
495
+
496
+ // Spawn enemies
497
+ spawnEnemies();
498
+
499
+ // Spawn treasures
500
+ spawnTreasures();
501
+ }
502
+
503
+ function connectHorizontally(x1, x2, y) {
504
+ const startX = Math.min(x1, x2);
505
+ const endX = Math.max(x1, x2);
506
+
507
+ for (let x = startX; x <= endX; x++) {
508
+ dungeonMap[y][x] = 1;
509
+ }
510
+ }
511
+
512
+ function connectVertically(y1, y2, x) {
513
+ const startY = Math.min(y1, y2);
514
+ const endY = Math.max(y1, y2);
515
+
516
+ for (let y = startY; y <= endY; y++) {
517
+ dungeonMap[y][x] = 1;
518
+ }
519
+ }
520
+
521
+ function spawnEnemies() {
522
+ const enemyTypes = ['slime', 'skeleton', 'bat'];
523
+ const enemyCount = 5 + Math.floor(Math.random() * 5) + floorCount;
524
+
525
+ for (let i = 0; i < enemyCount; i++) {
526
+ // Find a valid position
527
+ let x, y;
528
+ let validPosition = false;
529
+ let tries = 0;
530
+
531
+ while (!validPosition && tries < 100) {
532
+ tries++;
533
+
534
+ // Don't spawn in first or last room
535
+ const roomIndex = 1 + Math.floor(Math.random() * (gameState.rooms.length - 2));
536
+ const room = gameState.rooms[roomIndex];
537
+
538
+ x = room.x + Math.floor(Math.random() * room.width);
539
+ y = room.y + Math.floor(Math.random() * room.height);
540
+
541
+ // Check distance to player
542
+ const dx = x - gameState.player.x;
543
+ const dy = y - gameState.player.y;
544
+ const distance = Math.sqrt(dx * dx + dy * dy);
545
+
546
+ validPosition = dungeonMap[y][x] === 1 && distance > 3 &&
547
+ !gameState.enemies.some(e => e.x === x && e.y === y) &&
548
+ (gameState.staircases.down.x !== x || gameState.staircases.down.y !== y);
549
+ }
550
+
551
+ if (validPosition) {
552
+ const type = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
553
+ let health, speed, power;
554
+
555
+ // Set enemy stats
556
+ switch (type) {
557
+ case 'slime':
558
+ health = 30 + floorCount * 5;
559
+ speed = 1;
560
+ power = 10 + floorCount * 2;
561
+ break;
562
+ case 'skeleton':
563
+ health = 40 + floorCount * 5;
564
+ speed = 2;
565
+ power = 15 + floorCount * 2;
566
+ break;
567
+ case 'bat':
568
+ health = 20 + floorCount * 5;
569
+ speed = 3;
570
+ power = 8 + floorCount * 2;
571
+ break;
572
+ }
573
+
574
+ gameState.enemies.push({
575
+ x, y,
576
+ width: 30,
577
+ height: 30,
578
+ type,
579
+ health,
580
+ maxHealth: health,
581
+ speed,
582
+ power,
583
+ attackCooldown: 0,
584
+ direction: Math.random() > 0.5 ? 'left' : 'right',
585
+ damageTaken: 0,
586
+ state: 'idle'
587
+ });
588
+ }
589
+ }
590
+ }
591
+
592
+ function spawnTreasures() {
593
+ const treasureCount = 3 + Math.floor(Math.random() * 3);
594
+
595
+ for (let i = 0; i < treasureCount; i++) {
596
+ // Find a valid position
597
+ let x, y;
598
+ let validPosition = false;
599
+ let tries = 0;
600
+
601
+ while (!validPosition && tries < 100) {
602
+ tries++;
603
+
604
+ const roomIndex = Math.floor(Math.random() * gameState.rooms.length);
605
+ const room = gameState.rooms[roomIndex];
606
+
607
+ x = room.x + Math.floor(Math.random() * room.width);
608
+ y = room.y + Math.floor(Math.random() * room.height);
609
+
610
+ // Check distance to player and enemies
611
+ const dx = x - gameState.player.x;
612
+ const dy = y - gameState.player.y;
613
+ const distance = Math.sqrt(dx * dx + dy * dy);
614
+
615
+ validPosition = dungeonMap[y][x] === 1 && distance > 3 &&
616
+ !gameState.treasures.some(t => t.x === x && t.y === y) &&
617
+ (gameState.staircases.down.x !== x || gameState.staircases.down.y !== y);
618
+ }
619
+
620
+ if (validPosition) {
621
+ // Determine treasure type (70% gold, 15% health, 15% mana)
622
+ const rand = Math.random();
623
+ let type, value;
624
+
625
+ if (rand < 0.7) {
626
+ type = 'treasure';
627
+ value = 10 + Math.floor(Math.random() * 20) + floorCount * 5;
628
+ } else if (rand < 0.85) {
629
+ type = 'healthPotion';
630
+ value = 20 + floorCount * 3;
631
+ } else {
632
+ type = 'manaPotion';
633
+ value = 15 + floorCount * 3;
634
+ }
635
+
636
+ gameState.treasures.push({
637
+ x, y,
638
+ width: 30,
639
+ height: 30,
640
+ type,
641
+ value,
642
+ collected: false
643
+ });
644
+ }
645
+ }
646
+ }
647
+
648
+ // Game loop
649
+ function gameLoop(timestamp) {
650
+ if (!gameRunning) return;
651
+
652
+ const deltaTime = timestamp - lastTime;
653
+ lastTime = timestamp;
654
+
655
+ // Clear canvas
656
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
657
+
658
+ // Update game state
659
+ updateGame(deltaTime);
660
+
661
+ // Draw game
662
+ drawGame();
663
+
664
+ // Continue loop
665
+ requestAnimationFrame(gameLoop);
666
+ }
667
+
668
+ // Update game state
669
+ function updateGame(deltaTime) {
670
+ const player = gameState.player;
671
+ const timeFactor = deltaTime / 16; // Normalize to ~60fps
672
+
673
+ gameState.gameTime += deltaTime;
674
+
675
+ // Handle player movement
676
+ let moved = false;
677
+ let newX = player.x;
678
+ let newY = player.y;
679
+
680
+ if (gameState.keys.arrowright || gameState.keys.d) {
681
+ newX += player.speed * 0.1 * timeFactor;
682
+ player.direction = 'right';
683
+ moved = true;
684
+ }
685
+ if (gameState.keys.arrowleft || gameState.keys.a) {
686
+ newX -= player.speed * 0.1 * timeFactor;
687
+ player.direction = 'left';
688
+ moved = true;
689
+ }
690
+ if (gameState.keys.arrowup || gameState.keys.w) {
691
+ newY -= player.speed * 0.1 * timeFactor;
692
+ player.direction = 'up';
693
+ moved = true;
694
+ }
695
+ if (gameState.keys.arrowdown || gameState.keys.s) {
696
+ newY += player.speed * 0.1 * timeFactor;
697
+ player.direction = 'down';
698
+ moved = true;
699
+ }
700
+
701
+ // Check collision with walls
702
+ const tileX = Math.floor(newX);
703
+ const tileY = Math.floor(newY);
704
+
705
+ if (tileX >= 0 && tileX < mapWidth && tileY >= 0 && tileY < mapHeight) {
706
+ if (dungeonMap[tileY][tileX] === 1) {
707
+ player.x = newX;
708
+ player.y = newY;
709
+ }
710
+ }
711
+
712
+ // Update attack cooldown
713
+ if (player.attackCooldown > 0) {
714
+ player.attackCooldown -= timeFactor;
715
+ }
716
+
717
+ // Regenerate mana
718
+ if (player.mana < player.maxMana) {
719
+ player.mana = Math.min(player.maxMana, player.mana + 0.02 * timeFactor);
720
+ }
721
+
722
+ // Update enemies
723
+ for (let i = gameState.enemies.length - 1; i >= 0; i--) {
724
+ const enemy = gameState.enemies[i];
725
+
726
+ // Reset damage indicator
727
+ if (enemy.damageTaken > 0) {
728
+ enemy.damageTaken -= 0.05 * timeFactor;
729
+ }
730
+
731
+ // Check if enemy is dying
732
+ if (enemy.health <= 0) {
733
+ // Create death particles
734
+ for (let j = 0; j < 10; j++) {
735
+ gameState.particles.push({
736
+ x: enemy.x * tileSize + enemy.width / 2,
737
+ y: enemy.y * tileSize + enemy.height / 2,
738
+ size: 3 + Math.random() * 4,
739
+ color: enemy.type === 'slime' ? '#55ff55' :
740
+ enemy.type === 'skeleton' ? '#dddddd' : '#993399',
741
+ velocityX: -2 + Math.random() * 4,
742
+ velocityY: -2 + Math.random() * 4,
743
+ life: 30 + Math.random() * 20,
744
+ gravity: 0.1
745
+ });
746
+ }
747
+
748
+ // Remove enemy
749
+ gameState.enemies.splice(i, 1);
750
+ killCount++;
751
+ continue;
752
+ }
753
+
754
+ // Update attack cooldown
755
+ if (enemy.attackCooldown > 0) {
756
+ enemy.attackCooldown -= timeFactor;
757
+ }
758
+
759
+ // AI movement
760
+ const dx = player.x - enemy.x;
761
+ const dy = player.y - enemy.y;
762
+ const distance = Math.sqrt(dx * dx + dy * dy);
763
+
764
+ // Chase player if within range
765
+ if (distance < 8 && distance > 0.8) {
766
+ enemy.x += (dx / distance) * enemy.speed * 0.05 * timeFactor;
767
+ enemy.y += (dy / distance) * enemy.speed * 0.05 * timeFactor;
768
+
769
+ // Update direction
770
+ if (Math.abs(dx) > Math.abs(dy)) {
771
+ enemy.direction = dx > 0 ? 'right' : 'left';
772
+ } else {
773
+ enemy.direction = dy > 0 ? 'down' : 'up';
774
+ }
775
+
776
+ enemy.state = 'chasing';
777
+ } else {
778
+ // Random wandering
779
+ if (Math.random() < 0.01 * timeFactor) {
780
+ enemy.state = Math.random() > 0.5 ? 'idle' : 'wandering';
781
+ if (enemy.state === 'wandering') {
782
+ enemy.direction = ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)];
783
+ }
784
+ }
785
+
786
+ if (enemy.state === 'wandering') {
787
+ switch (enemy.direction) {
788
+ case 'up': enemy.y -= enemy.speed * 0.02 * timeFactor; break;
789
+ case 'down': enemy.y += enemy.speed * 0.02 * timeFactor; break;
790
+ case 'left': enemy.x -= enemy.speed * 0.02 * timeFactor; break;
791
+ case 'right': enemy.x += enemy.speed * 0.02 * timeFactor; break;
792
+ }
793
+
794
+ // Change direction if hitting a wall
795
+ const ex = Math.floor(enemy.x);
796
+ const ey = Math.floor(enemy.y);
797
+ if (ex <= 0 || ex >= mapWidth - 1 || ey <= 0 || ey >= mapHeight - 1 || dungeonMap[ey][ex] !== 1) {
798
+ enemy.direction = ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)];
799
+ }
800
+ }
801
+ }
802
+
803
+ // Attack player if close enough
804
+ if (distance < 1.2 && enemy.attackCooldown <= 0) {
805
+ player.health -= enemy.power;
806
+ enemy.attackCooldown = 60;
807
+
808
+ // Create hit particles
809
+ for (let j = 0; j < 5; j++) {
810
+ gameState.particles.push({
811
+ x: player.x * tileSize + player.width / 2,
812
+ y: player.y * tileSize + player.height / 2,
813
+ size: 2 + Math.random() * 3,
814
+ color: '#ff0000',
815
+ velocityX: -1 + Math.random() * 2,
816
+ velocityY: -1 + Math.random() * 2,
817
+ life: 15 + Math.random() * 10
818
+ });
819
+ }
820
+
821
+ // Check player death
822
+ if (player.health <= 0) {
823
+ gameOver(false);
824
+ }
825
+ }
826
+ }
827
+
828
+ // Check for treasure collection
829
+ for (let i = gameState.treasures.length - 1; i >= 0; i--) {
830
+ const treasure = gameState.treasures[i];
831
+ const dx = player.x - treasure.x;
832
+ const dy = player.y - treasure.y;
833
+ const distance = Math.sqrt(dx * dx + dy * dy);
834
+
835
+ if (distance < 1 && !treasure.collected) {
836
+ treasure.collected = true;
837
+
838
+ // Handle different treasure types
839
+ switch (treasure.type) {
840
+ case 'treasure':
841
+ goldCount += treasure.value;
842
+
843
+ // Create gold particles
844
+ for (let j = 0; j < 8; j++) {
845
+ gameState.particles.push({
846
+ x: treasure.x * tileSize + treasure.width / 2,
847
+ y: treasure.y * tileSize + treasure.height / 2,
848
+ size: 2 + Math.random() * 3,
849
+ color: '#ffd700',
850
+ velocityX: -1 + Math.random() * 2,
851
+ velocityY: -1 + Math.random() * 2,
852
+ life: 20 + Math.random() * 10
853
+ });
854
+ }
855
+ break;
856
+
857
+ case 'healthPotion':
858
+ player.health = Math.min(player.maxHealth, player.health + treasure.value);
859
+
860
+ // Create healing particles
861
+ for (let j = 0; j < 10; j++) {
862
+ gameState.particles.push({
863
+ x: treasure.x * tileSize + treasure.width / 2,
864
+ y: treasure.y * tileSize + treasure.height / 2,
865
+ size: 3 + Math.random() * 3,
866
+ color: '#ff3366',
867
+ velocityX: -1 + Math.random() * 2,
868
+ velocityY: -1 + Math.random() * 2,
869
+ life: 20 + Math.random() * 10
870
+ });
871
+ }
872
+ break;
873
+
874
+ case 'manaPotion':
875
+ player.mana = Math.min(player.maxMana, player.mana + treasure.value);
876
+
877
+ // Create mana particles
878
+ for (let j = 0; j < 10; j++) {
879
+ gameState.particles.push({
880
+ x: treasure.x * tileSize + treasure.width / 2,
881
+ y: treasure.y * tileSize + treasure.height / 2,
882
+ size: 3 + Math.random() * 3,
883
+ color: '#3399ff',
884
+ velocityX: -1 + Math.random() * 2,
885
+ velocityY: -1 + Math.random() * 2,
886
+ life: 20 + Math.random() * 10
887
+ });
888
+ }
889
+ break;
890
+ }
891
+
892
+ gameState.treasures.splice(i, 1);
893
+ }
894
+ }
895
+
896
+ // Check for staircase
897
+ if (gameState.staircases.down) {
898
+ const dx = player.x - gameState.staircases.down.x;
899
+ const dy = player.y - gameState.staircases.down.y;
900
+ const distance = Math.sqrt(dx * dx + dy * dy);
901
+
902
+ if (distance < 1) {
903
+ nextFloor();
904
+ }
905
+ }
906
+
907
+ // Update particles
908
+ for (let i = gameState.particles.length - 1; i >= 0; i--) {
909
+ const particle = gameState.particles[i];
910
+
911
+ particle.x += particle.velocityX * timeFactor;
912
+ particle.y += particle.velocityY * timeFactor;
913
+ if (particle.gravity) {
914
+ particle.velocityY += particle.gravity * timeFactor;
915
+ }
916
+ particle.life -= timeFactor;
917
+
918
+ if (particle.life <= 0) {
919
+ gameState.particles.splice(i, 1);
920
+ }
921
+ }
922
+
923
+ // Update UI
924
+ updateUI();
925
+ }
926
+
927
+ function attack() {
928
+ const player = gameState.player;
929
+
930
+ if (player.attackCooldown > 0) return;
931
+
932
+ player.attackCooldown = 20;
933
+
934
+ // Calculate attack area based on direction
935
+ let attackX = player.x;
936
+ let attackY = player.y;
937
+ let attackWidth = player.width / tileSize;
938
+ let attackHeight = player.height / tileSize;
939
+
940
+ switch (player.direction) {
941
+ case 'right':
942
+ attackX += 0.8;
943
+ attackWidth = 1.2;
944
+ attackHeight = 0.6;
945
+ break;
946
+ case 'left':
947
+ attackX -= 1.0;
948
+ attackWidth = 1.2;
949
+ attackHeight = 0.6;
950
+ break;
951
+ case 'up':
952
+ attackY -= 0.8;
953
+ attackWidth = 0.6;
954
+ attackHeight = 1.2;
955
+ break;
956
+ case 'down':
957
+ attackY += 0.8;
958
+ attackWidth = 0.6;
959
+ attackHeight = 1.2;
960
+ break;
961
+ }
962
+
963
+ // Check for enemies in attack range
964
+ let hitSomething = false;
965
+
966
+ for (let i = 0; i < gameState.enemies.length; i++) {
967
+ const enemy = gameState.enemies[i];
968
+
969
+ if (enemy.x < attackX + attackWidth &&
970
+ enemy.x + enemy.width/tileSize > attackX &&
971
+ enemy.y < attackY + attackHeight &&
972
+ enemy.y + enemy.height/tileSize > attackY) {
973
+
974
+ // Calculate damage (with potential critical for rogue)
975
+ let damage = player.attackPower;
976
+ if (player.class === 'rogue' && Math.random() < 0.3) {
977
+ damage *= 2; // Critical hit
978
+
979
+ // Create critical hit particles
980
+ for (let j = 0; j < 8; j++) {
981
+ gameState.particles.push({
982
+ x: enemy.x * tileSize + enemy.width / 2,
983
+ y: enemy.y * tileSize + enemy.height / 2,
984
+ size: 4 + Math.random() * 3,
985
+ color: '#ffff00',
986
+ velocityX: -1 + Math.random() * 2,
987
+ velocityY: -1 + Math.random() * 2,
988
+ life: 15 + Math.random() * 10
989
+ });
990
+ }
991
+ } else {
992
+ // Normal attack particles
993
+ for (let j = 0; j < 5; j++) {
994
+ gameState.particles.push({
995
+ x: enemy.x * tileSize + enemy.width / 2,
996
+ y: enemy.y * tileSize + enemy.height / 2,
997
+ size: 2 + Math.random() * 3,
998
+ color: '#ffffff',
999
+ velocityX: -1 + Math.random() * 2,
1000
+ velocityY: -1 + Math.random() * 2,
1001
+ life: 10 + Math.random() * 10
1002
+ });
1003
+ }
1004
+ }
1005
+
1006
+ enemy.health -= damage;
1007
+ enemy.damageTaken = 1;
1008
+ hitSomething = true;
1009
+ }
1010
+ }
1011
+
1012
+ // Create weapon swing effect if mage (ranged attack)
1013
+ if (player.class === 'mage' && player.mana >= 10) {
1014
+ player.mana -= 10;
1015
+
1016
+ // Create magic projectile
1017
+ gameState.particles.push({
1018
+ x: player.x * tileSize + player.width / 2,
1019
+ y: player.y * tileSize + player.height / 2,
1020
+ size: 8,
1021
+ color: '#9933ff',
1022
+ velocityX: (player.direction === 'right' ? 6 : player.direction === 'left' ? -6 : 0) * timeFactor,
1023
+ velocityY: (player.direction === 'down' ? 6 : player.direction === 'up' ? -6 : 0) * timeFactor,
1024
+ life: 60,
1025
+ isProjectile: true,
1026
+ power: player.attackPower * 0.8
1027
+ });
1028
+ }
1029
+
1030
+ // Play attack sound if hit something
1031
+ if (hitSomething) {
1032
+ // Would play sound here
1033
+ }
1034
+ }
1035
+
1036
+ function nextFloor() {
1037
+ floorCount++;
1038
+ document.getElementById('floorCount').textContent = floorCount;
1039
+ generateDungeon();
1040
+
1041
+ // Create transition particles
1042
+ for (let i = 0; i < -50; i++) {
1043
+ gameState.particles.push({
1044
+ x: Math.random() * canvas.width,
1045
+ y: Math.random() * canvas.height,
1046
+ size: 2 + Math.random() * 4,
1047
+ color: '#3399ff',
1048
+ velocityX: -1 + Math.random() * 2,
1049
+ velocityY: -1 + Math.random() * 2,
1050
+ life: 30 + Math.random() * 20
1051
+ });
1052
+ }
1053
+ }
1054
+
1055
+ // Draw game
1056
+ function drawGame() {
1057
+ const player = gameState.player;
1058
+
1059
+ // Draw dungeon tiles
1060
+ for (let y = 0; y < mapHeight; y++) {
1061
+ for (let x = 0; x < mapWidth; x++) {
1062
+ const screenX = x * tileSize;
1063
+ const screenY = y * tileSize;
1064
+
1065
+ if (dungeonMap[y][x] === 0) { // Wall
1066
+ ctx.fillStyle = '#333333';
1067
+ ctx.fillRect(screenX, screenY, tileSize, tileSize);
1068
+
1069
+ // Draw brick pattern
1070
+ ctx.fillStyle = '#444444';
1071
+ for (let by = 0; by < tileSize; by += 8) {
1072
+ for (let bx = (by / 8) % 2 === 0 ? 0 : 8; bx < tileSize; bx += 16) {
1073
+ ctx.fillRect(screenX + bx, screenY + by, 8, 4);
1074
+ }
1075
+ }
1076
+ } else { // Floor
1077
+ ctx.fillStyle = '#222222';
1078
+ ctx.fillRect(screenX, screenY, tileSize, tileSize);
1079
+
1080
+ // Draw floor pattern
1081
+ ctx.fillStyle = '#282828';
1082
+ if (x % 2 === y % 2) {
1083
+ ctx.fillRect(screenX + 10, screenY + 10, 3, 3);
1084
+ } else {
1085
+ ctx.fillRect(screenX + 20, screenY + 20, 3, 3);
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+
1091
+ // Draw staircases
1092
+ if (gameState.staircases.down) {
1093
+ const sx = gameState.staircases.down.x * tileSize;
1094
+ const sy = gameState.staircases.down.y * tileSize;
1095
+
1096
+ // Draw animated staircase
1097
+ ctx.fillStyle = '#8B4513';
1098
+ ctx.fillRect(sx, sy, tileSize, tileSize);
1099
+
1100
+ // Steps
1101
+ ctx.fillStyle = '#A0522D';
1102
+ for (let i = 0; i < 4; i++) {
1103
+ const offset = Math.sin(gameState.gameTime * 0.005 + i) * 2;
1104
+ ctx.fillRect(sx + i * 6, sy + i * 6, tileSize - i * 8, 6);
1105
+ }
1106
+
1107
+ // Glow
1108
+ ctx.fillStyle = 'rgba(255, 215, 0, ' + (0.3 + Math.sin(gameState.gameTime * 0.01) * 0.2) + ')';
1109
+ ctx.beginPath();
1110
+ ctx.arc(sx + tileSize/2, sy + tileSize/2, tileSize/2 + 5, 0, Math.PI * 2);
1111
+ ctx.fill();
1112
+ }
1113
+
1114
+ // Draw treasures
1115
+ for (let i = 0; i < gameState.treasures.length; i++) {
1116
+ const treasure = gameState.treasures[i];
1117
+ const screenX = treasure.x * tileSize;
1118
+ const screenY = treasure.y * tileSize;
1119
+
1120
+ // Draw treasure based on type with animation
1121
+ switch (treasure.type) {
1122
+ case 'treasure':
1123
+ // Chest base
1124
+ ctx.fillStyle = '#8B4513';
1125
+ ctx.fillRect(screenX + 5, screenY + 10, 22, 15);
1126
+
1127
+ // Chest top
1128
+ ctx.fillStyle = '#A0522D';
1129
+ ctx.beginPath();
1130
+ ctx.moveTo(screenX + 5, screenY + 10);
1131
+ ctx.lineTo(screenX + 27, screenY + 10);
1132
+ ctx.lineTo(screenX + 23, screenY + 5);
1133
+ ctx.lineTo(screenX + 9, screenY + 5);
1134
+ ctx.closePath();
1135
+ ctx.fill();
1136
+
1137
+ // Gold shine
1138
+ ctx.fillStyle = 'rgba(255, 215, 0, ' + (0.5 + Math.sin(gameState.gameTime * 0.01) * 0.4) + ')';
1139
+ ctx.beginPath();
1140
+ ctx.arc(screenX + 16, screenY + 12, 8, 0, Math.PI * 2);
1141
+ ctx.fill();
1142
+ break;
1143
+
1144
+ case 'healthPotion':
1145
+ // Bottle
1146
+ ctx.fillStyle = '#ff3366';
1147
+ ctx.beginPath();
1148
+ ctx.moveTo(screenX + 10, screenY + 25);
1149
+ ctx.lineTo(screenX + 22, screenY + 25);
1150
+ ctx.lineTo(screenX + 22, screenY + 10);
1151
+ ctx.quadraticCurveTo(screenX + 16, screenY + 5, screenX + 10, screenY + 10);
1152
+ ctx.closePath();
1153
+ ctx.fill();
1154
+
1155
+ // Liquid
1156
+ ctx.fillStyle = '#ff0066';
1157
+ const liquidHeight = 12 + Math.sin(gameState.gameTime * 0.02) * 2;
1158
+ ctx.fillRect(screenX + 12, screenY + 25 - liquidHeight, 8, liquidHeight);
1159
+
1160
+ // Glow
1161
+ ctx.fillStyle = 'rgba(255, 0, 102, ' + (0.2 + Math.sin(gameState.gameTime * 0.015) * 0.2) + ')';
1162
+ ctx.beginPath();
1163
+ ctx.arc(screenX + 16, screenY + 16, 12, 0, Math.PI * 2);
1164
+ ctx.fill();
1165
+ break;
1166
+
1167
+ case 'manaPotion':
1168
+ // Bottle
1169
+ ctx.fillStyle = '#3399ff';
1170
+ ctx.beginPath();
1171
+ ctx.moveTo(screenX + 10, screenY + 25);
1172
+ ctx.lineTo(screenX + 22, screenY + 25);
1173
+ ctx.lineTo(screenX + 22, screenY + 10);
1174
+ ctx.quadraticCurveTo(screenX + 16, screenY + 5, screenX + 10, screenY + 10);
1175
+ ctx.closePath();
1176
+ ctx.fill();
1177
+
1178
+ // Liquid
1179
+ ctx.fillStyle = '#3366ff';
1180
+ const manaHeight = 12 + Math.sin(gameState.gameTime * 0.025) * 2;
1181
+ ctx.fillRect(screenX + 12, screenY + 25 - manaHeight, 8, manaHeight);
1182
+
1183
+ // Glow
1184
+ ctx.fillStyle = 'rgba(51, 102, 255, ' + (0.2 + Math.sin(gameState.gameTime * 0.02) * 0.2) + ')';
1185
+ ctx.beginPath();
1186
+ ctx.arc(screenX + 16, screenY + 16, 12, 0, Math.PI * 2);
1187
+ ctx.fill();
1188
+ break;
1189
+ }
1190
+ }
1191
+
1192
+ // Draw enemies
1193
+ for (let i = 0; i < gameState.enemies.length; i++) {
1194
+ const enemy = gameState.enemies[i];
1195
+ const screenX = enemy.x * tileSize;
1196
+ const screenY = enemy.y * tileSize;
1197
+
1198
+ // Draw enemy based on type
1199
+ switch (enemy.type) {
1200
+ case 'slime':
1201
+ // Body
1202
+ ctx.fillStyle = '#55ff55';
1203
+ ctx.beginPath();
1204
+ ctx.ellipse(screenX + 15, screenY + 15, 15, 12 + Math.sin(gameState.gameTime * 0.02 + i) * 2, 0, 0, Math.PI * 2);
1205
+ ctx.fill();
1206
+
1207
+ // Eyes
1208
+ ctx.fillStyle = '#000000';
1209
+ const eyeOffset = Math.sin(gameState.gameTime * 0.05 + i) * 2;
1210
+ ctx.beginPath();
1211
+ ctx.arc(screenX + 8 + (enemy.direction === 'left' ? -eyeOffset : enemy.direction === 'right' ? eyeOffset : 0),
1212
+ screenY + 8, 3, 0, Math.PI * 2);
1213
+ ctx.arc(screenX + 20 + (enemy.direction === 'left' ? -eyeOffset : enemy.direction === 'right' ? eyeOffset : 0),
1214
+ screenY + 8, 3, 0, Math.PI * 2);
1215
+ ctx.fill();
1216
+
1217
+ // Mouth (only when chasing)
1218
+ if (enemy.state === 'chasing') {
1219
+ ctx.strokeStyle = '#000000';
1220
+ ctx.lineWidth = 2;
1221
+ ctx.beginPath();
1222
+ ctx.arc(screenX + 14, screenY + 15, 6, 0, Math.PI);
1223
+ ctx.stroke();
1224
+ }
1225
+
1226
+ // Damage flash
1227
+ if (enemy.damageTaken > 0) {
1228
+ ctx.fillStyle = 'rgba(255, 255, 255, ' + enemy.damageTaken * 0.7 + ')';
1229
+ ctx.beginPath();
1230
+ ctx.ellipse(screenX + 15, screenY + 15, 15, 12, 0, 0, Math.PI * 2);
1231
+ ctx.fill();
1232
+ }
1233
+ break;
1234
+
1235
+ case 'skeleton':
1236
+ // Head
1237
+ ctx.fillStyle = '#dddddd';
1238
+ ctx.beginPath();
1239
+ ctx.arc(screenX + 15, screenY + 10, 8, 0, Math.PI * 2);
1240
+ ctx.fill();
1241
+
1242
+ // Body
1243
+ ctx.fillRect(screenX + 10, screenY + 18, 10, 10);
1244
+
1245
+ // Arms
1246
+ const armX = enemy.direction === 'left' ? -3 : enemy.direction === 'right' ? 3 : 0;
1247
+ ctx.fillRect(screenX + 5 + armX, screenY + 20, 10, 4);
1248
+ ctx.fillRect(screenX + 15 - armX, screenY + 20, 10, 4);
1249
+
1250
+ // Legs
1251
+ ctx.fillRect(screenX + 10, screenY + 28, 5, 8);
1252
+ ctx.fillRect(screenX + 15, screenY + 28, 5, 8);
1253
+
1254
+ // Eye sockets
1255
+ ctx.fillStyle = '#000000';
1256
+ ctx.beginPath();
1257
+ ctx.arc(screenX + 12, screenY + 8, 2, 0, Math.PI * 2);
1258
+ ctx.arc(screenX + 18, screenY + 8, 2, 0, Math.PI * 2);
1259
+ ctx.fill();
1260
+
1261
+ // Damage flash
1262
+ if (enemy.damageTaken > 0) {
1263
+ ctx.fillStyle = 'rgba(255, 0, 0, ' + enemy.damageTaken * 0.5 + ')';
1264
+ ctx.beginPath();
1265
+ ctx.arc(screenX + 15, screenY + 15, 15, 0, Math.PI * 2);
1266
+ ctx.fill();
1267
+ }
1268
+ break;
1269
+
1270
+ case 'bat':
1271
+ // Wings
1272
+ const wingAngle = Math.sin(gameState.gameTime * 0.1 + i) * Math.PI / 4;
1273
+
1274
+ ctx.fillStyle = '#993399';
1275
+ ctx.save();
1276
+ ctx.translate(screenX + 15, screenY + 15);
1277
+ ctx.rotate(wingAngle);
1278
+ ctx.beginPath();
1279
+ ctx.ellipse(0, 0, 15, 5, 0, 0, Math.PI * 2);
1280
+ ctx.fill();
1281
+
1282
+ ctx.rotate(Math.PI);
1283
+ ctx.beginPath();
1284
+ ctx.ellipse(0, 0, 15, 5, 0, 0, Math.PI * 2);
1285
+ ctx.fill();
1286
+ ctx.restore();
1287
+
1288
+ // Body
1289
+ ctx.fillStyle = '#663366';
1290
+ ctx.beginPath();
1291
+ ctx.arc(screenX + 15, screenY + 15, 6, 0, Math.PI * 2);
1292
+ ctx.fill();
1293
+
1294
+ // Eyes
1295
+ ctx.fillStyle = '#ffffff';
1296
+ ctx.beginPath();
1297
+ ctx.arc(screenX + 12, screenY + 13, 2, 0, Math.PI * 2);
1298
+ ctx.arc(screenX + 18, screenY + 13, 2, 0, Math.PI * 2);
1299
+ ctx.fill();
1300
+
1301
+ // Damage flash
1302
+ if (enemy.damageTaken > 0) {
1303
+ ctx.fillStyle = 'rgba(255, 255, 255, ' + enemy.damageTaken * 0.7 + ')';
1304
+ ctx.beginPath();
1305
+ ctx.arc(screenX + 15, screenY + 15, 12, 0, Math.PI * 2);
1306
+ ctx.fill();
1307
+ }
1308
+ break;
1309
+ }
1310
+
1311
+ // Health bar
1312
+ if (enemy.health < enemy.maxHealth) {
1313
+ const healthWidth = 30 * (enemy.health / enemy.maxHealth);
1314
+ ctx.fillStyle = '#ff0000';
1315
+ ctx.fillRect(screenX, screenY - 10, healthWidth, 3);
1316
+ ctx.strokeStyle = '#000000';
1317
+ ctx.lineWidth = 1;
1318
+ ctx.strokeRect(screenX, screenY - 10, 30, 3);
1319
+ }
1320
+ }
1321
+
1322
+ // Draw particles (under player)
1323
+ for (let i = 0; i < gameState.particles.length; i++) {
1324
+ const particle = gameState.particles[i];
1325
+
1326
+ if (particle.isProjectile) {
1327
+ // Check for collision with enemies
1328
+ for (let j = 0; j < gameState.enemies.length; j++) {
1329
+ const enemy = gameState.enemies[j];
1330
+
1331
+ const ex = enemy.x * tileSize + enemy.width / 2;
1332
+ const ey = enemy.y * tileSize + enemy.height / 2;
1333
+ const dx = particle.x - ex;
1334
+ const dy = particle.y - ey;
1335
+ const distance = Math.sqrt(dx * dx + dy * dy);
1336
+
1337
+ if (distance < (enemy.width / 2 + particle.size / 2)) {
1338
+ enemy.health -= particle.power;
1339
+ enemy.damageTaken = 1;
1340
+
1341
+ // Create hit effect
1342
+ for (let k = 0; k < 10; k++) {
1343
+ gameState.particles.push({
1344
+ x: particle.x,
1345
+ y: particle.y,
1346
+ size: 2 + Math.random() * 3,
1347
+ color: '#ffffff',
1348
+ velocityX: -2 + Math.random() * 4,
1349
+ velocityY: -2 + Math.random() * 4,
1350
+ life: 15 + Math.random() * 10
1351
+ });
1352
+ }
1353
+
1354
+ particle.life = 0;
1355
+ break;
1356
+ }
1357
+ }
1358
+
1359
+ // Draw magic projectile
1360
+ ctx.fillStyle = particle.color;
1361
+ ctx.beginPath();
1362
+ ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
1363
+ ctx.fill();
1364
+
1365
+ // Glow effect
1366
+ ctx.fillStyle = 'rgba(153, 51, 255, 0.3)';
1367
+ ctx.beginPath();
1368
+ ctx.arc(particle.x, particle.y, particle.size * 1.5, 0, Math.PI * 2);
1369
+ ctx.fill();
1370
+ } else if (particle.y < player.y * tileSize + player.height / 2) {
1371
+ // Only draw particles below player here (others will be drawn above)
1372
+ drawParticle(particle);
1373
+ }
1374
+ }
1375
+
1376
+ // Draw player
1377
+ const screenX = player.x * tileSize;
1378
+ const screenY = player.y * tileSize;
1379
+
1380
+ // Shadow
1381
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
1382
+ ctx.beginPath();
1383
+ ctx.ellipse(screenX + 15, screenY + 30, 10, 4, 0, 0, Math.PI * 2);
1384
+ ctx.fill();
1385
+
1386
+ // Draw player character
1387
+ drawPlayerCharacter(screenX, screenY, player);
1388
+
1389
+ // Draw particles (above player)
1390
+ for (let i = 0; i < gameState.particles.length; i++) {
1391
+ const particle = gameState.particles[i];
1392
+
1393
+ if (!particle.isProjectile && particle.y >= player.y * tileSize + player.height / 2) {
1394
+ drawParticle(particle);
1395
+ }
1396
+ }
1397
+ }
1398
+
1399
+ function drawPlayerCharacter(x, y, player) {
1400
+ // Draw based on class and direction
1401
+ switch (player.class) {
1402
+ case 'warrior':
1403
+ // Body
1404
+ ctx.fillStyle = '#ff5555';
1405
+ ctx.fillRect(x + 8, y + 10, 14, 18);
1406
+
1407
+ // Head
1408
+ ctx.fillStyle = '#ffccaa';
1409
+ ctx.beginPath();
1410
+ ctx.arc(x + 15, y + 8, 7, 0, Math.PI * 2);
1411
+ ctx.fill();
1412
+
1413
+ // Hair
1414
+ ctx.fillStyle = '#663300';
1415
+ if (player.direction === 'left' || player.direction === 'right') {
1416
+ ctx.beginPath();
1417
+ ctx.arc(x + 15, y + 6, 8, 0, Math.PI);
1418
+ ctx.fill();
1419
+ } else {
1420
+ ctx.beginPath();
1421
+ ctx.arc(x + 15, y + 6, 8, 0, Math.PI * 2);
1422
+ ctx.fill();
1423
+ }
1424
+
1425
+ // Shield
1426
+ if (player.direction === 'left') {
1427
+ ctx.fillStyle = '#8B4513';
1428
+ ctx.fillRect(x, y + 15, 6, 16);
1429
+ ctx.fillStyle = '#A0522D';
1430
+ ctx.fillRect(x + 1, y + 16, 4, 14);
1431
+ }
1432
+
1433
+ // Sword
1434
+ if (player.direction === 'right') {
1435
+ ctx.fillStyle = '#cccccc';
1436
+ ctx.fillRect(x + 22, y + 15, 12, 3);
1437
+ ctx.fillStyle = '#999999';
1438
+ ctx.fillRect(x + 34, y + 16, 2, 1);
1439
+ }
1440
+
1441
+ // Armor details
1442
+ ctx.fillStyle = '#993333';
1443
+ ctx.fillRect(x + 10, y + 15, 10, 3);
1444
+
1445
+ break;
1446
+
1447
+ case 'rogue':
1448
+ // Body
1449
+ ctx.fillStyle = '#33aa33';
1450
+ ctx.fillRect(x + 8, y + 10, 14, 18);
1451
+
1452
+ // Head
1453
+ ctx.fillStyle = '#ffccaa';
1454
+ ctx.beginPath();
1455
+ ctx.arc(x + 15, y + 8, 7, 0, Math.PI * 2);
1456
+ ctx.fill();
1457
+
1458
+ // Mask
1459
+ ctx.fillStyle = '#334433';
1460
+ if (player.direction === 'left' || player.direction === 'right') {
1461
+ ctx.beginPath();
1462
+ ctx.moveTo(x + 15, y + 6);
1463
+ ctx.lineTo(x + 8, y + 11);
1464
+ ctx.lineTo(x + 8, y + 15);
1465
+ ctx.lineTo(x + 15, y + 10);
1466
+ ctx.fill();
1467
+ } else {
1468
+ ctx.fillRect(x + 8, y + 10, 14, 4);
1469
+ }
1470
+
1471
+ // Dagger
1472
+ if (player.attackCooldown > 0) {
1473
+ // Draw dagger in attack motion
1474
+ ctx.fillStyle = '#cccccc';
1475
+ const attackOffset = 10 * (1 - player.attackCooldown / 20);
1476
+ ctx.save();
1477
+ ctx.translate(x + 15, y + 15);
1478
+ ctx.rotate(Math.PI / 4 * (1 - player.attackCooldown / 20));
1479
+ ctx.fillRect(5 + attackOffset, -1.5, 12, 3);
1480
+ ctx.restore();
1481
+ } else {
1482
+ // Draw sheathed dagger
1483
+ ctx.fillStyle = '#333333';
1484
+ ctx.fillRect(x + 20, y + 20, 2, 8);
1485
+ }
1486
+
1487
+ // Belt
1488
+ ctx.fillStyle = '#000000';
1489
+ ctx.fillRect(x + 10, y + 25, 10, 2);
1490
+
1491
+ break;
1492
+
1493
+ case 'mage':
1494
+ // Robe
1495
+ ctx.fillStyle = '#3355aa';
1496
+ ctx.beginPath();
1497
+ ctx.moveTo(x + 8, y + 28);
1498
+ ctx.lineTo(x + 22, y + 28);
1499
+ ctx.lineTo(x + 20, y + 10);
1500
+ ctx.lineTo(x + 10, y + 10);
1501
+ ctx.closePath();
1502
+ ctx.fill();
1503
+
1504
+ // Head
1505
+ ctx.fillStyle = '#ffccaa';
1506
+ ctx.beginPath();
1507
+ ctx.arc(x + 15, y + 8, 7, 0, Math.PI * 2);
1508
+ ctx.fill();
1509
+
1510
+ // Hat
1511
+ ctx.fillStyle = '#6600cc';
1512
+ ctx.beginPath();
1513
+ ctx.moveTo(x + 8, y + 8);
1514
+ ctx.lineTo(x + 22, y + 8);
1515
+ ctx.lineTo(x + 15, y + 2);
1516
+ ctx.closePath();
1517
+ ctx.fill();
1518
+
1519
+ // Staff
1520
+ ctx.fillStyle = '#8B4513';
1521
+ ctx.fillRect(x + 5, y + 5, 3, 25);
1522
+
1523
+ // Orb
1524
+ if (player.attackCooldown > 0) {
1525
+ // Draw orb with glow when attacking
1526
+ ctx.fillStyle = '#9933ff';
1527
+ ctx.beginPath();
1528
+ ctx.arc(x + 6, y + 10, 5, 0, Math.PI * 2);
1529
+ ctx.fill();
1530
+
1531
+ ctx.fillStyle = 'rgba(153, 51, 255, 0.5)';
1532
+ ctx.beginPath();
1533
+ ctx.arc(x + 6, y + 10, 8, 0, Math.PI * 2);
1534
+ ctx.fill();
1535
+ }
1536
+
1537
+ break;
1538
+ }
1539
+
1540
+ // Draw attack effect if attacking
1541
+ if (player.attackCooldown > 15) {
1542
+ const attackProgress = (20 - player.attackCooldown) / 5;
1543
+
1544
+ // Draw sword swing effect for warrior
1545
+ if (player.class === 'warrior' && (player.direction === 'left' || player.direction === 'right')) {
1546
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
1547
+ ctx.lineWidth = 2 + attackProgress;
1548
+ ctx.beginPath();
1549
+ ctx.moveTo(x + 25, y + 18);
1550
+ ctx.lineTo(x + 25 + 30 * attackProgress, y + 18 - 10 * attackProgress);
1551
+ ctx.stroke();
1552
+ }
1553
+
1554
+ // Draw dagger slash effect for rogue
1555
+ if (player.class === 'rogue') {
1556
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
1557
+ ctx.lineWidth = 1 + attackProgress;
1558
+ ctx.beginPath();
1559
+ ctx.moveTo(x + 18, y + 15);
1560
+ ctx.lineTo(x + 18 + 20 * attackProgress, y + 15 - 15 * attackProgress);
1561
+ ctx.stroke();
1562
+ }
1563
+ }
1564
+
1565
+ // Draw health bar if damaged
1566
+ if (player.health < player.maxHealth) {
1567
+ const healthWidth = 30 * (player.health / player.maxHealth);
1568
+ ctx.fillStyle = '#ff0000';
1569
+ ctx.fillRect(x, y - 10, healthWidth, 3);
1570
+ ctx.strokeStyle = '#000000';
1571
+ ctx.lineWidth = 1;
1572
+ ctx.strokeRect(x, y - 10, 30, 3);
1573
+ }
1574
+ }
1575
+
1576
+ function drawParticle(particle) {
1577
+ ctx.fillStyle = particle.color;
1578
+ ctx.globalAlpha = Math.min(1, particle.life / 20);
1579
+ ctx.beginPath();
1580
+ ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
1581
+ ctx.fill();
1582
+ ctx.globalAlpha = 1;
1583
+ }
1584
+
1585
+ // Update UI
1586
+ function updateUI() {
1587
+ const player = gameState.player;
1588
+
1589
+ // Update health and mana bars
1590
+ document.getElementById('healthText').textContent = `${Math.floor(player.health)}/${player.maxHealth}`;
1591
+ document.getElementById('manaText').textContent = `${Math.floor(player.mana)}/${player.maxMana}`;
1592
+ document.getElementById('healthBar').style.width = `${(player.health / player.maxHealth) * 100}%`;
1593
+ document.getElementById('manaBar').style.width = `${(player.mana / player.maxMana) * 100}%`;
1594
+
1595
+ // Update gold and kill count
1596
+ document.getElementById('goldCount').textContent = goldCount;
1597
+ document.getElementById('killCount').textContent = killCount;
1598
+ }
1599
+
1600
+ function gameOver(victory) {
1601
+ gameRunning = false;
1602
+
1603
+ // Create game over overlay
1604
+ const overlay = document.createElement('div');
1605
+ overlay.className = 'fixed inset-0 bg-black bg-opacity-80 flex flex-col items-center justify-center z-50';
1606
+ overlay.innerHTML = `
1607
+ <div class="bg-gray-800 rounded-lg p-8 max-w-md w-full text-center">
1608
+ <h2 class="text-3xl font-bold mb-4 ${victory ? 'text-yellow-400' : 'text-red-500'}">
1609
+ ${victory ? 'Victory!' : 'Game Over'}
1610
+ </h2>
1611
+ <p class="mb-4">You ${victory ? 'conquered' : 'explored'} ${floorCount} floors.</p>
1612
+ <p class="mb-6">You collected ${goldCount} gold and defeated ${killCount} enemies.</p>
1613
+ <div class="flex justify-center space-x-4">
1614
+ <button id="restartGameBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded">
1615
+ Play Again
1616
+ </button>
1617
+ <button id="returnToMenuBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded">
1618
+ Main Menu
1619
+ </button>
1620
+ </div>
1621
+ </div>
1622
+ `;
1623
+
1624
+ document.body.appendChild(overlay);
1625
+
1626
+ // Button event listeners
1627
+ document.getElementById('restartGameBtn').addEventListener('click', () => {
1628
+ document.body.removeChild(overlay);
1629
+ restartGame();
1630
+ });
1631
+
1632
+ document.getElementById('returnToMenuBtn').addEventListener('click', () => {
1633
+ document.body.removeChild(overlay);
1634
+ returnToMenu();
1635
+ });
1636
+ }
1637
+
1638
+ function restartGame() {
1639
+ // Reset game state
1640
+ goldCount = 0;
1641
+ killCount = 0;
1642
+ floorCount = 1;
1643
+ gameState.player.health = gameState.player.maxHealth;
1644
+ gameState.player.mana = gameState.player.maxMana;
1645
+
1646
+ // Update UI
1647
+ document.getElementById('goldCount').textContent = goldCount;
1648
+ document.getElementById('killCount').textContent = killCount;
1649
+ document.getElementById('floorCount').textContent = floorCount;
1650
+ updateUI();
1651
+
1652
+ // Generate new dungeon
1653
+ generateDungeon();
1654
+
1655
+ // Restart game loop
1656
+ gameRunning = true;
1657
+ lastTime = performance.now();
1658
+ requestAnimationFrame(gameLoop);
1659
+ }
1660
+
1661
+ function returnToMenu() {
1662
+ document.getElementById('characterSelection').classList.remove('hidden');
1663
+ document.getElementById('gameScreen').classList.add('hidden');
1664
+ gameRunning = false;
1665
+ }
1666
+ </script>
1667
+ <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=etnom/gauntlet" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
1668
+ </html>