jamesjun commited on
Commit
f6abe44
·
verified ·
1 Parent(s): 730734b

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +6 -4
  2. index.html +1430 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Modded Tower Defense
3
- emoji: 🐢
4
- colorFrom: pink
5
  colorTo: blue
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: modded-tower-defense
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,1430 @@
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>Tower Defense Game</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
+ #game-container {
11
+ position: relative;
12
+ width: 800px;
13
+ height: 600px;
14
+ background-color: #2d3748;
15
+ overflow: hidden;
16
+ }
17
+
18
+ .cell {
19
+ width: 40px;
20
+ height: 40px;
21
+ position: absolute;
22
+ border: 1px solid rgba(255, 255, 255, 0.1);
23
+ }
24
+
25
+ .path {
26
+ background-color: #4a5568;
27
+ }
28
+
29
+ .tower {
30
+ width: 36px;
31
+ height: 36px;
32
+ border-radius: 50%;
33
+ position: absolute;
34
+ display: flex;
35
+ justify-content: center;
36
+ align-items: center;
37
+ color: white;
38
+ font-weight: bold;
39
+ cursor: pointer;
40
+ z-index: 10;
41
+ }
42
+
43
+ .tower::after {
44
+ content: '';
45
+ position: absolute;
46
+ width: 80px;
47
+ height: 80px;
48
+ border-radius: 50%;
49
+ opacity: 0.2;
50
+ transform: translate(-50%, -50%);
51
+ top: 50%;
52
+ left: 50%;
53
+ }
54
+
55
+ .tower-1 {
56
+ background-color: #4299e1;
57
+ }
58
+
59
+ .tower-1::after {
60
+ background-color: #4299e1;
61
+ }
62
+
63
+ .tower-2 {
64
+ background-color: #f56565;
65
+ }
66
+
67
+ .tower-2::after {
68
+ background-color: #f56565;
69
+ }
70
+
71
+ .tower-3 {
72
+ background-color: #68d391;
73
+ }
74
+
75
+ .tower-3::after {
76
+ background-color: #68d391;
77
+ }
78
+
79
+ .tower-4 {
80
+ background-color: #9f7aea;
81
+ }
82
+
83
+ .tower-4::after {
84
+ background-color: #9f7aea;
85
+ }
86
+
87
+ .tower-5 {
88
+ background-color: #ed8936;
89
+ }
90
+
91
+ .tower-5::after {
92
+ background-color: #ed8936;
93
+ }
94
+
95
+ .tower-6 {
96
+ background-color: #f6e05e;
97
+ }
98
+
99
+ .tower-6::after {
100
+ background-color: #f6e05e;
101
+ }
102
+
103
+ .enemy {
104
+ width: 30px;
105
+ height: 30px;
106
+ border-radius: 50%;
107
+ position: absolute;
108
+ display: flex;
109
+ justify-content: center;
110
+ align-items: center;
111
+ color: white;
112
+ font-weight: bold;
113
+ z-index: 5;
114
+ transition: left 0.1s linear, top 0.1s linear;
115
+ }
116
+
117
+ .enemy-1 {
118
+ background-color: #ecc94b;
119
+ }
120
+
121
+ .enemy-2 {
122
+ background-color: #ed8936;
123
+ }
124
+
125
+ .enemy-3 {
126
+ background-color: #9f7aea;
127
+ }
128
+
129
+ .projectile {
130
+ position: absolute;
131
+ width: 8px;
132
+ height: 8px;
133
+ border-radius: 50%;
134
+ z-index: 8;
135
+ }
136
+
137
+ .projectile-1 {
138
+ background-color: #4299e1;
139
+ }
140
+
141
+ .projectile-2 {
142
+ background-color: #f56565;
143
+ }
144
+
145
+ .projectile-3 {
146
+ background-color: #68d391;
147
+ }
148
+
149
+ .projectile-4 {
150
+ background-color: #9f7aea;
151
+ }
152
+
153
+ .projectile-5 {
154
+ background-color: #ed8936;
155
+ }
156
+
157
+ .projectile-6 {
158
+ background-color: #f6e05e;
159
+ }
160
+
161
+ .range-indicator {
162
+ position: absolute;
163
+ border: 2px dashed rgba(255, 255, 255, 0.5);
164
+ border-radius: 50%;
165
+ transform: translate(-50%, -50%);
166
+ pointer-events: none;
167
+ z-index: 1;
168
+ }
169
+
170
+ #tower-menu {
171
+ position: absolute;
172
+ background-color: rgba(26, 32, 44, 0.9);
173
+ border-radius: 8px;
174
+ padding: 10px;
175
+ display: none;
176
+ z-index: 100;
177
+ min-width: 180px;
178
+ }
179
+
180
+ .health-bar {
181
+ height: 4px;
182
+ background-color: #48bb78;
183
+ position: absolute;
184
+ top: -8px;
185
+ left: 0;
186
+ width: 100%;
187
+ }
188
+
189
+ #game-over {
190
+ position: absolute;
191
+ top: 0;
192
+ left: 0;
193
+ width: 100%;
194
+ height: 100%;
195
+ background-color: rgba(0, 0, 0, 0.8);
196
+ display: none;
197
+ justify-content: center;
198
+ align-items: center;
199
+ flex-direction: column;
200
+ z-index: 200;
201
+ }
202
+
203
+ .splash-effect {
204
+ position: absolute;
205
+ width: 30px;
206
+ height: 30px;
207
+ border-radius: 50%;
208
+ opacity: 0.5;
209
+ animation: splash 0.5s ease-out;
210
+ transform: translate(-50%, -50%);
211
+ z-index: 7;
212
+ }
213
+
214
+ @keyframes splash {
215
+ 0% { transform: scale(0.1); opacity: 0.8; }
216
+ 100% { transform: scale(3); opacity: 0; }
217
+ }
218
+
219
+ .frost-effect {
220
+ position: absolute;
221
+ width: 100%;
222
+ height: 100%;
223
+ background-color: rgba(147, 197, 253, 0.3);
224
+ border-radius: 50%;
225
+ z-index: 6;
226
+ animation: frost 0.5s ease-out;
227
+ }
228
+
229
+ @keyframes frost {
230
+ 0% { transform: scale(0.1); opacity: 0.8; }
231
+ 100% { transform: scale(3); opacity: 0; }
232
+ }
233
+
234
+ .lightning-effect {
235
+ position: absolute;
236
+ width: 10px;
237
+ height: 40px;
238
+ background-color: #f6e05e;
239
+ z-index: 7;
240
+ animation: lightning 0.2s linear;
241
+ }
242
+
243
+ @keyframes lightning {
244
+ 0% { transform: scaleY(0.1); opacity: 0.8; }
245
+ 50% { transform: scaleY(1); opacity: 1; }
246
+ 100% { transform: scaleY(0.1); opacity: 0; }
247
+ }
248
+ </style>
249
+ </head>
250
+ <body class="bg-gray-900 text-white flex flex-col items-center p-4">
251
+ <h1 class="text-3xl font-bold mb-4">Tower Defense</h1>
252
+
253
+ <div class="flex justify-between w-full max-w-4xl mb-4">
254
+ <div class="flex space-x-4">
255
+ <div class="bg-gray-800 p-3 rounded-lg">
256
+ <span class="font-bold">Wave:</span> <span id="wave">1</span>
257
+ </div>
258
+ <div class="bg-gray-800 p-3 rounded-lg">
259
+ <span class="font-bold">Lives:</span> <span id="lives">20</span>
260
+ </div>
261
+ <div class="bg-gray-800 p-3 rounded-lg">
262
+ <span class="font-bold">Money:</span> $<span id="money">100</span>
263
+ </div>
264
+ </div>
265
+ <div class="flex space-x-2">
266
+ <button id="start-wave" class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg font-bold">
267
+ Start Wave
268
+ </button>
269
+ <button id="restart-wave" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg font-bold">
270
+ Restart Wave
271
+ </button>
272
+ </div>
273
+ </div>
274
+
275
+ <div id="game-container" class="rounded-lg shadow-xl">
276
+ <!-- Game elements will be added here dynamically -->
277
+ </div>
278
+
279
+ <div class="mt-4 w-full max-w-4xl">
280
+ <h2 class="text-xl font-bold mb-2">Tower Shop</h2>
281
+ <div class="grid grid-cols-3 gap-4">
282
+ <div class="bg-gray-800 p-4 rounded-lg cursor-pointer hover:bg-gray-700 tower-shop-item" data-type="1">
283
+ <div class="flex items-center mb-2">
284
+ <div class="tower-1 w-8 h-8 mr-2 flex items-center justify-center">
285
+ <i class="fas fa-bolt text-white"></i>
286
+ </div>
287
+ <h3 class="font-bold">Lightning Tower</h3>
288
+ </div>
289
+ <p class="text-sm text-gray-300">Cost: $50</p>
290
+ <p class="text-sm text-gray-300">Damage: 10</p>
291
+ <p class="text-sm text-gray-300">Range: 120px</p>
292
+ <p class="text-xs text-blue-300">Fast attacker</p>
293
+ </div>
294
+ <div class="bg-gray-800 p-4 rounded-lg cursor-pointer hover:bg-gray-700 tower-shop-item" data-type="2">
295
+ <div class="flex items-center mb-2">
296
+ <div class="tower-2 w-8 h-8 mr-2 flex items-center justify-center">
297
+ <i class="fas fa-fire text-white"></i>
298
+ </div>
299
+ <h3 class="font-bold">Flame Tower</h3>
300
+ </div>
301
+ <p class="text-sm text-gray-300">Cost: $100</p>
302
+ <p class="text-sm text-gray-300">Damage: 20</p>
303
+ <p class="text-sm text-gray-300">Range: 90px</p>
304
+ <p class="text-xs text-red-300">Splash damage</p>
305
+ </div>
306
+ <div class="bg-gray-800 p-4 rounded-lg cursor-pointer hover:bg-gray-700 tower-shop-item" data-type="3">
307
+ <div class="flex items-center mb-2">
308
+ <div class="tower-3 w-8 h-8 mr-2 flex items-center justify-center">
309
+ <i class="fas fa-leaf text-white"></i>
310
+ </div>
311
+ <h3 class="font-bold">Nature Tower</h3>
312
+ </div>
313
+ <p class="text-sm text-gray-300">Cost: $100</p>
314
+ <p class="text-sm text-gray-300">Damage: 15</p>
315
+ <p class="text-sm text-gray-300">Range: 150px</p>
316
+ <p class="text-xs text-green-300">Poison effect</p>
317
+ </div>
318
+ <div class="bg-gray-800 p-4 rounded-lg cursor-pointer hover:bg-gray-700 tower-shop-item" data-type="4">
319
+ <div class="flex items-center mb-2">
320
+ <div class="tower-4 w-8 h-8 mr-2 flex items-center justify-center">
321
+ <i class="fas fa-snowflake text-white"></i>
322
+ </div>
323
+ <h3 class="font-bold">Frost Tower</h3>
324
+ </div>
325
+ <p class="text-sm text-gray-300">Cost: $120</p>
326
+ <p class="text-sm text-gray-300">Damage: 8</p>
327
+ <p class="text-sm text-gray-300">Range: 130px</p>
328
+ <p class="text-xs text-purple-300">Slows enemies</p>
329
+ </div>
330
+ <div class="bg-gray-800 p-4 rounded-lg cursor-pointer hover:bg-gray-700 tower-shop-item" data-type="5">
331
+ <div class="flex items-center mb-2">
332
+ <div class="tower-5 w-8 h-8 mr-2 flex items-center justify-center">
333
+ <i class="fas fa-bomb text-white"></i>
334
+ </div>
335
+ <h3 class="font-bold">Bomb Tower</h3>
336
+ </div>
337
+ <p class="text-sm text-gray-300">Cost: $150</p>
338
+ <p class="text-sm text-gray-300">Damage: 40</p>
339
+ <p class="text-sm text-gray-300">Range: 70px</p>
340
+ <p class="text-xs text-orange-300">Area damage</p>
341
+ </div>
342
+ <div class="bg-gray-800 p-4 rounded-lg cursor-pointer hover:bg-gray-700 tower-shop-item" data-type="6">
343
+ <div class="flex items-center mb-2">
344
+ <div class="tower-6 w-8 h-8 mr-2 flex items-center justify-center">
345
+ <i class="fas fa-bolt-lightning text-white"></i>
346
+ </div>
347
+ <h3 class="font-bold">Tesla Tower</h3>
348
+ </div>
349
+ <p class="text-sm text-gray-300">Cost: $200</p>
350
+ <p class="text-sm text-gray-300">Damage: 25</p>
351
+ <p class="text-sm text-gray-300">Range: 110px</p>
352
+ <p class="text-xs text-yellow-300">Chains to enemies</p>
353
+ </div>
354
+ </div>
355
+ </div>
356
+
357
+ <div id="tower-menu" class="text-sm">
358
+ <div class="flex justify-between items-center mb-2">
359
+ <h3 class="font-bold" id="tower-menu-title">Tower</h3>
360
+ <button id="sell-tower" class="bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-xs">Sell</button>
361
+ </div>
362
+ <div class="mb-2">
363
+ <p>Level: <span id="tower-level">1</span></p>
364
+ <p>Damage: <span id="tower-damage">10</span></p>
365
+ <p>Range: <span id="tower-range">120</span>px</p>
366
+ <p id="tower-special" class="text-xs"></p>
367
+ </div>
368
+ <button id="upgrade-tower" class="bg-blue-600 hover:bg-blue-700 w-full py-1 rounded">Upgrade ($<span id="upgrade-cost">50</span>)</button>
369
+ </div>
370
+
371
+ <div id="game-over" class="text-center">
372
+ <h2 class="text-4xl font-bold mb-4">Game Over</h2>
373
+ <p class="text-xl mb-6">You survived <span id="final-wave">0</span> waves!</p>
374
+ <button id="restart-game" class="bg-green-600 hover:bg-green-700 px-6 py-3 rounded-lg font-bold text-lg">
375
+ Play Again
376
+ </button>
377
+ </div>
378
+
379
+ <script>
380
+ // Game state
381
+ const gameState = {
382
+ gridWidth: 20,
383
+ gridHeight: 15,
384
+ cellSize: 40,
385
+ money: 100,
386
+ lives: 20,
387
+ wave: 1,
388
+ gameActive: false,
389
+ placingTower: false,
390
+ towerType: null,
391
+ selectedTower: null,
392
+ enemies: [],
393
+ towers: [],
394
+ projectiles: [],
395
+ effects: [],
396
+ path: [
397
+ {x: 0, y: 7},
398
+ {x: 5, y: 7},
399
+ {x: 5, y: 3},
400
+ {x: 10, y: 3},
401
+ {x: 10, y: 10},
402
+ {x: 15, y: 10},
403
+ {x: 15, y: 5},
404
+ {x: 20, y: 5}
405
+ ],
406
+ enemySpawnInterval: null,
407
+ gameLoopInterval: null,
408
+ currentWaveEnemies: 0,
409
+ totalWaveEnemies: 0
410
+ };
411
+
412
+ // Tower types
413
+ const towerTypes = {
414
+ 1: {
415
+ name: "Lightning Tower",
416
+ cost: 50,
417
+ damage: 10,
418
+ range: 120,
419
+ color: "blue",
420
+ upgradeCost: 50,
421
+ projectileSpeed: 12,
422
+ cooldown: 20, // Frames between shots
423
+ icon: "fa-bolt",
424
+ special: "Fast attack speed",
425
+ effect: "lightning"
426
+ },
427
+ 2: {
428
+ name: "Flame Tower",
429
+ cost: 100,
430
+ damage: 20,
431
+ range: 90,
432
+ color: "red",
433
+ upgradeCost: 75,
434
+ projectileSpeed: 6,
435
+ cooldown: 45,
436
+ icon: "fa-fire",
437
+ special: "Splash damage to nearby enemies",
438
+ effect: "flame",
439
+ splashRadius: 40
440
+ },
441
+ 3: {
442
+ name: "Nature Tower",
443
+ cost: 100,
444
+ damage: 15,
445
+ range: 150,
446
+ color: "green",
447
+ upgradeCost: 60,
448
+ projectileSpeed: 8,
449
+ cooldown: 40,
450
+ icon: "fa-leaf",
451
+ special: "Applies poison over time",
452
+ effect: "poison"
453
+ },
454
+ 4: {
455
+ name: "Frost Tower",
456
+ cost: 120,
457
+ damage: 8,
458
+ range: 130,
459
+ color: "purple",
460
+ upgradeCost: 60,
461
+ projectileSpeed: 10,
462
+ cooldown: 35,
463
+ icon: "fa-snowflake",
464
+ special: "Slows enemies by 30% for 2s",
465
+ effect: "frost"
466
+ },
467
+ 5: {
468
+ name: "Bomb Tower",
469
+ cost: 150,
470
+ damage: 40,
471
+ range: 70,
472
+ color: "orange",
473
+ upgradeCost: 90,
474
+ projectileSpeed: 4,
475
+ cooldown: 60,
476
+ icon: "fa-bomb",
477
+ special: "High damage in an area",
478
+ effect: "explosion",
479
+ splashRadius: 60
480
+ },
481
+ 6: {
482
+ name: "Tesla Tower",
483
+ cost: 200,
484
+ damage: 25,
485
+ range: 110,
486
+ color: "yellow",
487
+ upgradeCost: 100,
488
+ projectileSpeed: 15,
489
+ cooldown: 50,
490
+ icon: "fa-bolt-lightning",
491
+ special: "Chains damage to 2 nearby enemies",
492
+ effect: "chain"
493
+ }
494
+ };
495
+
496
+ // Enemy types
497
+ const enemyTypes = [
498
+ {
499
+ health: 50,
500
+ speed: 1.5,
501
+ reward: 10,
502
+ color: "yellow"
503
+ },
504
+ {
505
+ health: 100,
506
+ speed: 1,
507
+ reward: 20,
508
+ color: "orange"
509
+ },
510
+ {
511
+ health: 200,
512
+ speed: 0.7,
513
+ reward: 40,
514
+ color: "purple"
515
+ }
516
+ ];
517
+
518
+ // DOM elements
519
+ const gameContainer = document.getElementById('game-container');
520
+ const waveDisplay = document.getElementById('wave');
521
+ const livesDisplay = document.getElementById('lives');
522
+ const moneyDisplay = document.getElementById('money');
523
+ const startWaveBtn = document.getElementById('start-wave');
524
+ const restartWaveBtn = document.getElementById('restart-wave');
525
+ const towerShopItems = document.querySelectorAll('.tower-shop-item');
526
+ const towerMenu = document.getElementById('tower-menu');
527
+ const towerMenuTitle = document.getElementById('tower-menu-title');
528
+ const towerLevel = document.getElementById('tower-level');
529
+ const towerDamage = document.getElementById('tower-damage');
530
+ const towerRange = document.getElementById('tower-range');
531
+ const towerSpecial = document.getElementById('tower-special');
532
+ const upgradeCost = document.getElementById('upgrade-cost');
533
+ const upgradeBtn = document.getElementById('upgrade-tower');
534
+ const sellBtn = document.getElementById('sell-tower');
535
+ const gameOverScreen = document.getElementById('game-over');
536
+ const finalWaveDisplay = document.getElementById('final-wave');
537
+ const restartBtn = document.getElementById('restart-game');
538
+
539
+ // Initialize game
540
+ function initGame() {
541
+ // Clear previous game state
542
+ gameContainer.innerHTML = '';
543
+ gameState.enemies = [];
544
+ gameState.towers = [];
545
+ gameState.projectiles = [];
546
+ gameState.effects = [];
547
+
548
+ // Reset game state
549
+ gameState.money = 100;
550
+ gameState.lives = 20;
551
+ gameState.wave = 1;
552
+ gameState.gameActive = false;
553
+ gameState.placingTower = false;
554
+ gameState.towerType = null;
555
+ gameState.selectedTower = null;
556
+ gameState.currentWaveEnemies = 0;
557
+ gameState.totalWaveEnemies = 0;
558
+
559
+ // Update UI
560
+ updateUI();
561
+
562
+ // Create grid
563
+ createGrid();
564
+
565
+ // Create path
566
+ createPath();
567
+
568
+ // Hide game over screen
569
+ gameOverScreen.style.display = 'none';
570
+
571
+ // Enable buttons
572
+ startWaveBtn.disabled = false;
573
+ restartWaveBtn.disabled = false;
574
+
575
+ // Start game loop
576
+ if (gameState.gameLoopInterval) {
577
+ clearInterval(gameState.gameLoopInterval);
578
+ }
579
+ gameState.gameLoopInterval = setInterval(gameLoop, 16); // ~60fps
580
+ }
581
+
582
+ // Create grid
583
+ function createGrid() {
584
+ for (let y = 0; y < gameState.gridHeight; y++) {
585
+ for (let x = 0; x < gameState.gridWidth; x++) {
586
+ const cell = document.createElement('div');
587
+ cell.className = 'cell';
588
+ cell.style.left = `${x * gameState.cellSize}px`;
589
+ cell.style.top = `${y * gameState.cellSize}px`;
590
+ cell.dataset.x = x;
591
+ cell.dataset.y = y;
592
+
593
+ // Add click event for tower placement
594
+ cell.addEventListener('click', () => {
595
+ if (gameState.placingTower) {
596
+ placeTower(x, y);
597
+ }
598
+ });
599
+
600
+ gameContainer.appendChild(cell);
601
+ }
602
+ }
603
+ }
604
+
605
+ // Create path
606
+ function createPath() {
607
+ // Draw path cells
608
+ for (let i = 0; i < gameState.path.length - 1; i++) {
609
+ const start = gameState.path[i];
610
+ const end = gameState.path[i + 1];
611
+
612
+ // Horizontal path
613
+ if (start.y === end.y) {
614
+ const direction = start.x < end.x ? 1 : -1;
615
+ for (let x = start.x; x !== end.x; x += direction) {
616
+ if (x >= 0 && x < gameState.gridWidth && start.y >= 0 && start.y < gameState.gridHeight) {
617
+ const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${start.y}"]`);
618
+ if (cell) cell.classList.add('path');
619
+ }
620
+ }
621
+ }
622
+ // Vertical path
623
+ else if (start.x === end.x) {
624
+ const direction = start.y < end.y ? 1 : -1;
625
+ for (let y = start.y; y !== end.y; y += direction) {
626
+ if (start.x >= 0 && start.x < gameState.gridWidth && y >= 0 && y < gameState.gridHeight) {
627
+ const cell = document.querySelector(`.cell[data-x="${start.x}"][data-y="${y}"]`);
628
+ if (cell) cell.classList.add('path');
629
+ }
630
+ }
631
+ }
632
+ }
633
+
634
+ // Add the last cell
635
+ const last = gameState.path[gameState.path.length - 1];
636
+ if (last.x >= 0 && last.x < gameState.gridWidth && last.y >= 0 && last.y < gameState.gridHeight) {
637
+ const cell = document.querySelector(`.cell[data-x="${last.x}"][data-y="${last.y}"]`);
638
+ if (cell) cell.classList.add('path');
639
+ }
640
+ }
641
+
642
+ // Place tower
643
+ function placeTower(x, y) {
644
+ // Check if cell is empty and not on path
645
+ const cell = document.querySelector(`.cell[data-x="${x}"][data-y="${y}"]`);
646
+ if (!cell || cell.classList.contains('path') || cell.classList.contains('has-tower')) {
647
+ return;
648
+ }
649
+
650
+ // Check if player has enough money
651
+ const towerCost = towerTypes[gameState.towerType].cost;
652
+ if (gameState.money < towerCost) {
653
+ alert('Not enough money!');
654
+ return;
655
+ }
656
+
657
+ // Deduct money
658
+ gameState.money -= towerCost;
659
+ updateUI();
660
+
661
+ // Create tower
662
+ const tower = {
663
+ type: gameState.towerType,
664
+ x: x,
665
+ y: y,
666
+ level: 1,
667
+ damage: towerTypes[gameState.towerType].damage,
668
+ range: towerTypes[gameState.towerType].range,
669
+ cooldown: 0,
670
+ maxCooldown: towerTypes[gameState.towerType].cooldown,
671
+ effect: towerTypes[gameState.towerType].effect,
672
+ splashRadius: towerTypes[gameState.towerType].splashRadius || 0
673
+ };
674
+
675
+ gameState.towers.push(tower);
676
+
677
+ // Create tower element
678
+ const towerElement = document.createElement('div');
679
+ towerElement.className = `tower tower-${tower.type}`;
680
+ towerElement.style.left = `${x * gameState.cellSize + 2}px`;
681
+ towerElement.style.top = `${y * gameState.cellSize + 2}px`;
682
+ towerElement.dataset.index = gameState.towers.length - 1;
683
+
684
+ // Add icon to tower
685
+ const icon = document.createElement('i');
686
+ icon.className = `fas ${towerTypes[tower.type].icon}`;
687
+ towerElement.appendChild(icon);
688
+
689
+ // Add click event for tower selection
690
+ towerElement.addEventListener('click', (e) => {
691
+ e.stopPropagation();
692
+ selectTower(gameState.towers.length - 1);
693
+ });
694
+
695
+ gameContainer.appendChild(towerElement);
696
+
697
+ // Mark cell as occupied
698
+ cell.classList.add('has-tower');
699
+
700
+ // Exit tower placement mode
701
+ gameState.placingTower = false;
702
+ gameState.towerType = null;
703
+
704
+ // Remove range indicator if it exists
705
+ const rangeIndicator = document.querySelector('.range-indicator');
706
+ if (rangeIndicator) rangeIndicator.remove();
707
+ }
708
+
709
+ // Select tower
710
+ function selectTower(index) {
711
+ // Close menu if clicking the same tower
712
+ if (gameState.selectedTower === index && towerMenu.style.display === 'block') {
713
+ towerMenu.style.display = 'none';
714
+ gameState.selectedTower = null;
715
+
716
+ // Remove range indicator
717
+ const rangeIndicator = document.querySelector('.range-indicator');
718
+ if (rangeIndicator) rangeIndicator.remove();
719
+ return;
720
+ }
721
+
722
+ gameState.selectedTower = index;
723
+ const tower = gameState.towers[index];
724
+
725
+ // Update tower menu
726
+ towerMenuTitle.textContent = towerTypes[tower.type].name;
727
+ towerLevel.textContent = tower.level;
728
+ towerDamage.textContent = tower.damage;
729
+ towerRange.textContent = tower.range;
730
+ towerSpecial.textContent = towerTypes[tower.type].special;
731
+ upgradeCost.textContent = towerTypes[tower.type].upgradeCost * tower.level;
732
+
733
+ // Position menu near tower
734
+ const menuX = tower.x * gameState.cellSize + gameState.cellSize;
735
+ const menuY = tower.y * gameState.cellSize;
736
+
737
+ // Adjust if near right edge
738
+ if (menuX + 180 > gameContainer.offsetWidth) {
739
+ towerMenu.style.left = `${tower.x * gameState.cellSize - 180}px`;
740
+ } else {
741
+ towerMenu.style.left = `${menuX}px`;
742
+ }
743
+
744
+ // Adjust if near bottom edge
745
+ if (menuY + 150 > gameContainer.offsetHeight) {
746
+ towerMenu.style.top = `${tower.y * gameState.cellSize - 100}px`;
747
+ } else {
748
+ towerMenu.style.top = `${menuY}px`;
749
+ }
750
+
751
+ towerMenu.style.display = 'block';
752
+
753
+ // Show range indicator
754
+ const rangeIndicator = document.createElement('div');
755
+ rangeIndicator.className = 'range-indicator';
756
+ rangeIndicator.style.width = `${tower.range * 2}px`;
757
+ rangeIndicator.style.height = `${tower.range * 2}px`;
758
+ rangeIndicator.style.left = `${tower.x * gameState.cellSize + gameState.cellSize / 2}px`;
759
+ rangeIndicator.style.top = `${tower.y * gameState.cellSize + gameState.cellSize / 2}px`;
760
+ gameContainer.appendChild(rangeIndicator);
761
+ }
762
+
763
+ // Upgrade tower
764
+ function upgradeTower() {
765
+ if (gameState.selectedTower === null) return;
766
+
767
+ const tower = gameState.towers[gameState.selectedTower];
768
+ const cost = towerTypes[tower.type].upgradeCost * tower.level;
769
+
770
+ if (gameState.money >= cost) {
771
+ gameState.money -= cost;
772
+ tower.level += 1;
773
+ tower.damage = Math.floor(towerTypes[tower.type].damage * (1 + (tower.level - 1) * 0.5));
774
+ tower.range = Math.floor(towerTypes[tower.type].range * (1 + (tower.level - 1) * 0.2));
775
+
776
+ if (tower.splashRadius > 0) {
777
+ tower.splashRadius = Math.floor(tower.splashRadius * (1 + (tower.level - 1) * 0.1));
778
+ }
779
+
780
+ // Update tower menu
781
+ towerLevel.textContent = tower.level;
782
+ towerDamage.textContent = tower.damage;
783
+ towerRange.textContent = tower.range;
784
+ upgradeCost.textContent = towerTypes[tower.type].upgradeCost * tower.level;
785
+
786
+ // Update range indicator
787
+ const rangeIndicator = document.querySelector('.range-indicator');
788
+ if (rangeIndicator) {
789
+ rangeIndicator.style.width = `${tower.range * 2}px`;
790
+ rangeIndicator.style.height = `${tower.range * 2}px`;
791
+ }
792
+
793
+ updateUI();
794
+ } else {
795
+ alert('Not enough money!');
796
+ }
797
+ }
798
+
799
+ // Sell tower
800
+ function sellTower() {
801
+ if (gameState.selectedTower === null) return;
802
+
803
+ const tower = gameState.towers[gameState.selectedTower];
804
+ const refund = Math.floor(towerTypes[tower.type].cost * 0.7 * tower.level);
805
+
806
+ gameState.money += refund;
807
+
808
+ // Remove tower element
809
+ const towerElement = document.querySelector(`.tower[data-index="${gameState.selectedTower}"]`);
810
+ towerElement.remove();
811
+
812
+ // Remove range indicator
813
+ const rangeIndicator = document.querySelector('.range-indicator');
814
+ if (rangeIndicator) rangeIndicator.remove();
815
+
816
+ // Mark cell as empty
817
+ const cell = document.querySelector(`.cell[data-x="${tower.x}"][data-y="${tower.y}"]`);
818
+ cell.classList.remove('has-tower');
819
+
820
+ // Remove tower from array
821
+ gameState.towers.splice(gameState.selectedTower, 1);
822
+
823
+ // Update all tower elements' data-index attributes
824
+ document.querySelectorAll('.tower').forEach((el, index) => {
825
+ el.dataset.index = index;
826
+ });
827
+
828
+ // Close menu
829
+ towerMenu.style.display = 'none';
830
+ gameState.selectedTower = null;
831
+
832
+ updateUI();
833
+ }
834
+
835
+ // Start wave
836
+ function startWave() {
837
+ if (gameState.gameActive) return;
838
+
839
+ gameState.gameActive = true;
840
+ startWaveBtn.disabled = true;
841
+ restartWaveBtn.disabled = true;
842
+ gameState.currentWaveEnemies = 0;
843
+
844
+ // Calculate total enemies for this wave
845
+ gameState.totalWaveEnemies = Math.floor(5 + gameState.wave * 1.5);
846
+
847
+ // Spawn enemies
848
+ let enemyType = Math.min(Math.floor(gameState.wave / 5), 2); // Stronger enemies every 5 waves
849
+
850
+ gameState.enemySpawnInterval = setInterval(() => {
851
+ if (gameState.currentWaveEnemies >= gameState.totalWaveEnemies) {
852
+ clearInterval(gameState.enemySpawnInterval);
853
+ gameState.enemySpawnInterval = null;
854
+ return;
855
+ }
856
+
857
+ spawnEnemy(enemyType);
858
+ gameState.currentWaveEnemies++;
859
+
860
+ // Every 3 enemies, increase type if possible
861
+ if (gameState.currentWaveEnemies % 3 === 0 && enemyType < 2) {
862
+ enemyType++;
863
+ }
864
+ }, 1000);
865
+ }
866
+
867
+ // Restart current wave
868
+ function restartWave() {
869
+ // Clear existing enemies and projectiles
870
+ gameState.enemies.forEach((enemy, index) => {
871
+ const enemyElement = document.querySelectorAll('.enemy')[index];
872
+ if (enemyElement) enemyElement.remove();
873
+ });
874
+ gameState.enemies = [];
875
+
876
+ gameState.projectiles.forEach((projectile, index) => {
877
+ const projectileElement = document.querySelectorAll('.projectile')[index];
878
+ if (projectileElement) projectileElement.remove();
879
+ });
880
+ gameState.projectiles = [];
881
+
882
+ // Clear any active spawn interval
883
+ if (gameState.enemySpawnInterval) {
884
+ clearInterval(gameState.enemySpawnInterval);
885
+ gameState.enemySpawnInterval = null;
886
+ }
887
+
888
+ // Reset wave state
889
+ gameState.gameActive = false;
890
+ gameState.currentWaveEnemies = 0;
891
+
892
+ // Enable start wave button
893
+ startWaveBtn.disabled = false;
894
+ restartWaveBtn.disabled = false;
895
+ }
896
+
897
+ // Spawn enemy
898
+ function spawnEnemy(type) {
899
+ const enemy = {
900
+ type: type,
901
+ health: enemyTypes[type].health,
902
+ maxHealth: enemyTypes[type].health,
903
+ speed: enemyTypes[type].speed,
904
+ reward: enemyTypes[type].reward,
905
+ pathIndex: 0,
906
+ x: gameState.path[0].x * gameState.cellSize + gameState.cellSize / 2,
907
+ y: gameState.path[0].y * gameState.cellSize + gameState.cellSize / 2,
908
+ statusEffects: []
909
+ };
910
+
911
+ gameState.enemies.push(enemy);
912
+
913
+ // Create enemy element
914
+ const enemyElement = document.createElement('div');
915
+ enemyElement.className = `enemy enemy-${type + 1}`;
916
+ enemyElement.style.left = `${enemy.x - 15}px`;
917
+ enemyElement.style.top = `${enemy.y - 15}px`;
918
+
919
+ // Add health bar
920
+ const healthBar = document.createElement('div');
921
+ healthBar.className = 'health-bar';
922
+ enemyElement.appendChild(healthBar);
923
+
924
+ gameContainer.appendChild(enemyElement);
925
+ }
926
+
927
+ // Create effect
928
+ function createEffect(x, y, type) {
929
+ const effect = { x, y, type, frame: 0, maxFrames: 30 };
930
+ gameState.effects.push(effect);
931
+
932
+ let effectElement;
933
+
934
+ switch(type) {
935
+ case 'flame':
936
+ effectElement = document.createElement('div');
937
+ effectElement.className = 'splash-effect';
938
+ effectElement.style.backgroundColor = '#f56565';
939
+ break;
940
+ case 'frost':
941
+ effectElement = document.createElement('div');
942
+ effectElement.className = 'frost-effect';
943
+ break;
944
+ case 'lightning':
945
+ effectElement = document.createElement('div');
946
+ effectElement.className = 'lightning-effect';
947
+ break;
948
+ case 'explosion':
949
+ effectElement = document.createElement('div');
950
+ effectElement.className = 'splash-effect';
951
+ effectElement.style.backgroundColor = '#ed8936';
952
+ effect.maxFrames = 20;
953
+ break;
954
+ case 'poison':
955
+ effectElement = document.createElement('div');
956
+ effectElement.className = 'splash-effect';
957
+ effectElement.style.backgroundColor = '#68d391';
958
+ effect.maxFrames = 40;
959
+ break;
960
+ default:
961
+ effectElement = document.createElement('div');
962
+ effectElement.className = 'splash-effect';
963
+ effectElement.style.backgroundColor = '#4299e1';
964
+ }
965
+
966
+ effectElement.style.left = `${x}px`;
967
+ effectElement.style.top = `${y}px`;
968
+
969
+ gameContainer.appendChild(effectElement);
970
+
971
+ return effectElement;
972
+ }
973
+
974
+ // Apply status effect
975
+ function applyStatusEffect(enemy, effectType) {
976
+ if (!enemy.statusEffects.includes(effectType)) {
977
+ enemy.statusEffects.push(effectType);
978
+
979
+ switch(effectType) {
980
+ case 'frost':
981
+ enemy.speed *= 0.7; // Slow by 30%
982
+ break;
983
+ case 'poison':
984
+ // Poison effect will be handled in game loop
985
+ break;
986
+ }
987
+
988
+ // Remove effect after duration
989
+ setTimeout(() => {
990
+ const index = enemy.statusEffects.indexOf(effectType);
991
+ if (index !== -1) {
992
+ enemy.statusEffects.splice(index, 1);
993
+
994
+ // Restore original speed for frost
995
+ if (effectType === 'frost') {
996
+ const originalSpeed = enemyTypes[enemy.type].speed;
997
+ enemy.speed = originalSpeed;
998
+ }
999
+ }
1000
+ }, 2000); // 2 second duration
1001
+ }
1002
+ }
1003
+
1004
+ // Game loop
1005
+ function gameLoop() {
1006
+ // Process effects
1007
+ for (let i = gameState.effects.length - 1; i >= 0; i--) {
1008
+ const effect = gameState.effects[i];
1009
+ effect.frame++;
1010
+
1011
+ if (effect.frame >= effect.maxFrames) {
1012
+ gameState.effects.splice(i, 1);
1013
+ }
1014
+ }
1015
+
1016
+ // Move enemies and handle status effects
1017
+ gameState.enemies.forEach((enemy, enemyIndex) => {
1018
+ // Handle poison damage
1019
+ if (enemy.statusEffects.includes('poison')) {
1020
+ enemy.health -= 1;
1021
+
1022
+ // Update health bar
1023
+ const enemyElement = document.querySelectorAll('.enemy')[enemyIndex];
1024
+ if (enemyElement) {
1025
+ const healthBar = enemyElement.querySelector('.health-bar');
1026
+ healthBar.style.width = `${(enemy.health / enemy.maxHealth) * 100}%`;
1027
+
1028
+ if (enemy.health <= 0) {
1029
+ // Enemy died
1030
+ gameState.money += enemy.reward;
1031
+ updateUI();
1032
+ removeEnemy(enemyIndex);
1033
+ }
1034
+ }
1035
+ }
1036
+
1037
+ // Skip if enemy is dead
1038
+ if (enemy.health <= 0) return;
1039
+
1040
+ const target = gameState.path[enemy.pathIndex + 1];
1041
+ if (!target) {
1042
+ // Enemy reached the end
1043
+ gameState.lives--;
1044
+ updateUI();
1045
+ removeEnemy(enemyIndex);
1046
+
1047
+ if (gameState.lives <= 0) {
1048
+ gameOver();
1049
+ }
1050
+ return;
1051
+ }
1052
+
1053
+ const targetX = target.x * gameState.cellSize + gameState.cellSize / 2;
1054
+ const targetY = target.y * gameState.cellSize + gameState.cellSize / 2;
1055
+
1056
+ const dx = targetX - enemy.x;
1057
+ const dy = targetY - enemy.y;
1058
+ const distance = Math.sqrt(dx * dx + dy * dy);
1059
+
1060
+ if (distance < 2) {
1061
+ // Reached current target, move to next
1062
+ enemy.pathIndex++;
1063
+ } else {
1064
+ // Move toward target (accounting for speed reductions)
1065
+ const effectiveSpeed = enemy.speed * (enemy.statusEffects.includes('frost') ? 0.7 : 1);
1066
+ enemy.x += (dx / distance) * effectiveSpeed;
1067
+ enemy.y += (dy / distance) * effectiveSpeed;
1068
+
1069
+ // Update enemy element position
1070
+ const enemyElement = document.querySelectorAll('.enemy')[enemyIndex];
1071
+ if (enemyElement) {
1072
+ enemyElement.style.left = `${enemy.x - 15}px`;
1073
+ enemyElement.style.top = `${enemy.y - 15}px`;
1074
+
1075
+ // Show frost visual effect if slowed
1076
+ if (enemy.statusEffects.includes('frost')) {
1077
+ if (!enemyElement.querySelector('.frost-visual')) {
1078
+ const frostEffect = document.createElement('div');
1079
+ frostEffect.className = 'absolute frost-visual';
1080
+ frostEffect.style.width = '30px';
1081
+ frostEffect.style.height = '30px';
1082
+ frostEffect.style.borderRadius = '50%';
1083
+ frostEffect.style.backgroundColor = 'rgba(147, 197, 253, 0.3)';
1084
+ frostEffect.style.zIndex = '6';
1085
+ enemyElement.appendChild(frostEffect);
1086
+ }
1087
+ } else {
1088
+ const frostVisual = enemyElement.querySelector('.frost-visual');
1089
+ if (frostVisual) frostVisual.remove();
1090
+ }
1091
+ }
1092
+ }
1093
+ });
1094
+
1095
+ // Tower actions
1096
+ gameState.towers.forEach((tower, towerIndex) => {
1097
+ if (tower.cooldown > 0) {
1098
+ tower.cooldown--;
1099
+ return;
1100
+ }
1101
+
1102
+ // Find closest enemy in range
1103
+ let closestEnemy = null;
1104
+ let closestDistance = Infinity;
1105
+
1106
+ gameState.enemies.forEach((enemy, enemyIndex) => {
1107
+ if (enemy.health <= 0) return;
1108
+
1109
+ const dx = enemy.x - (tower.x * gameState.cellSize + gameState.cellSize / 2);
1110
+ const dy = enemy.y - (tower.y * gameState.cellSize + gameState.cellSize / 2);
1111
+ const distance = Math.sqrt(dx * dx + dy * dy);
1112
+
1113
+ if (distance < tower.range && distance < closestDistance) {
1114
+ closestDistance = distance;
1115
+ closestEnemy = { enemy, enemyIndex };
1116
+ }
1117
+ });
1118
+
1119
+ if (closestEnemy) {
1120
+ // Shoot at enemy
1121
+ tower.cooldown = tower.maxCooldown;
1122
+
1123
+ // Handle different tower effects
1124
+ if (tower.effect === 'chain') {
1125
+ // Tesla tower - hits multiple enemies
1126
+ const hitEnemies = [{ enemy: closestEnemy.enemy, enemyIndex: closestEnemy.enemyIndex }];
1127
+
1128
+ // Find additional enemies in range
1129
+ gameState.enemies.forEach((enemy, enemyIndex) => {
1130
+ if (enemyIndex === closestEnemy.enemyIndex || enemy.health <= 0) return;
1131
+
1132
+ const dx = enemy.x - closestEnemy.enemy.x;
1133
+ const dy = enemy.y - closestEnemy.enemy.y;
1134
+ const distance = Math.sqrt(dx * dx + dy * dy);
1135
+
1136
+ if (distance < 60 && hitEnemies.length < 3) { // Chain to 2 additional enemies
1137
+ hitEnemies.push({ enemy, enemyIndex });
1138
+ }
1139
+ });
1140
+
1141
+ // Create projectiles for each hit enemy
1142
+ hitEnemies.forEach((target, i) => {
1143
+ // Create projectile
1144
+ const projectile = {
1145
+ towerIndex: towerIndex,
1146
+ enemyIndex: target.enemyIndex,
1147
+ x: tower.x * gameState.cellSize + gameState.cellSize / 2,
1148
+ y: tower.y * gameState.cellSize + gameState.cellSize / 2,
1149
+ targetX: target.enemy.x,
1150
+ targetY: target.enemy.y,
1151
+ speed: towerTypes[tower.type].projectileSpeed,
1152
+ damage: tower.damage * (i === 0 ? 1 : 0.6), // Main target takes full damage, chained take reduced
1153
+ effect: tower.effect
1154
+ };
1155
+
1156
+ gameState.projectiles.push(projectile);
1157
+
1158
+ // Create projectile element
1159
+ const projectileElement = document.createElement('div');
1160
+ projectileElement.className = `projectile projectile-${tower.type}`;
1161
+ projectileElement.style.left = `${projectile.x - 4}px`;
1162
+ projectileElement.style.top = `${projectile.y - 4}px`;
1163
+ gameContainer.appendChild(projectileElement);
1164
+ });
1165
+ } else {
1166
+ // Regular projectile towers
1167
+ const projectile = {
1168
+ towerIndex: towerIndex,
1169
+ enemyIndex: closestEnemy.enemyIndex,
1170
+ x: tower.x * gameState.cellSize + gameState.cellSize / 2,
1171
+ y: tower.y * gameState.cellSize + gameState.cellSize / 2,
1172
+ targetX: closestEnemy.enemy.x,
1173
+ targetY: closestEnemy.enemy.y,
1174
+ speed: towerTypes[tower.type].projectileSpeed,
1175
+ damage: tower.damage,
1176
+ effect: tower.effect,
1177
+ splashRadius: tower.splashRadius
1178
+ };
1179
+
1180
+ gameState.projectiles.push(projectile);
1181
+
1182
+ // Create projectile element
1183
+ const projectileElement = document.createElement('div');
1184
+ projectileElement.className = `projectile projectile-${tower.type}`;
1185
+ projectileElement.style.left = `${projectile.x - 4}px`;
1186
+ projectileElement.style.top = `${projectile.y - 4}px`;
1187
+ gameContainer.appendChild(projectileElement);
1188
+ }
1189
+ }
1190
+ });
1191
+
1192
+ // Move projectiles and handle hits
1193
+ gameState.projectiles.forEach((projectile, projectileIndex) => {
1194
+ const enemy = gameState.enemies[projectile.enemyIndex];
1195
+ if (!enemy || enemy.health <= 0) {
1196
+ // Enemy died before projectile hit
1197
+ removeProjectile(projectileIndex);
1198
+ return;
1199
+ }
1200
+
1201
+ // Update target position (enemy may have moved)
1202
+ projectile.targetX = enemy.x;
1203
+ projectile.targetY = enemy.y;
1204
+
1205
+ const dx = projectile.targetX - projectile.x;
1206
+ const dy = projectile.targetY - projectile.y;
1207
+ const distance = Math.sqrt(dx * dx + dy * dy);
1208
+
1209
+ if (distance < 5) {
1210
+ // Hit enemy - handle effects
1211
+ if (projectile.effect === 'frost') {
1212
+ applyStatusEffect(enemy, 'frost');
1213
+ createEffect(enemy.x, enemy.y, 'frost');
1214
+ } else if (projectile.effect === 'poison') {
1215
+ applyStatusEffect(enemy, 'poison');
1216
+ createEffect(enemy.x, enemy.y, 'poison');
1217
+ } else if (projectile.effect === 'flame') {
1218
+ // Flame splash damage
1219
+ createEffect(enemy.x, enemy.y, 'flame');
1220
+
1221
+ gameState.enemies.forEach((e, idx) => {
1222
+ if (idx === projectile.enemyIndex || e.health <= 0) return;
1223
+
1224
+ const edx = e.x - enemy.x;
1225
+ const edy = e.y - enemy.y;
1226
+ const edist = Math.sqrt(edx * edx + edy * edy);
1227
+
1228
+ if (edist < projectile.splashRadius) {
1229
+ e.health -= projectile.damage * 0.4; // 40% splash damage
1230
+
1231
+ // Update health bar
1232
+ const enemyElement = document.querySelectorAll('.enemy')[idx];
1233
+ if (enemyElement) {
1234
+ const healthBar = enemyElement.querySelector('.health-bar');
1235
+ healthBar.style.width = `${(e.health / e.maxHealth) * 100}%`;
1236
+
1237
+ if (e.health <= 0) {
1238
+ // Enemy died
1239
+ gameState.money += e.reward;
1240
+ updateUI();
1241
+ removeEnemy(idx);
1242
+ }
1243
+ }
1244
+ }
1245
+ });
1246
+ } else if (projectile.effect === 'explosion') {
1247
+ // Bomb tower area damage
1248
+ createEffect(enemy.x, enemy.y, 'explosion');
1249
+
1250
+ gameState.enemies.forEach((e, idx) => {
1251
+ const edx = e.x - enemy.x;
1252
+ const edy = e.y - enemy.y;
1253
+ const edist = Math.sqrt(edx * edx + edy * edy);
1254
+
1255
+ if (edist < projectile.splashRadius && e.health > 0) {
1256
+ const splashDamage = projectile.damage * (1 - (edist / projectile.splashRadius));
1257
+ e.health -= splashDamage;
1258
+
1259
+ // Update health bar
1260
+ const enemyElement = document.querySelectorAll('.enemy')[idx];
1261
+ if (enemyElement) {
1262
+ const healthBar = enemyElement.querySelector('.health-bar');
1263
+ healthBar.style.width = `${(e.health / e.maxHealth) * 100}%`;
1264
+
1265
+ if (e.health <= 0) {
1266
+ // Enemy died
1267
+ gameState.money += e.reward;
1268
+ updateUI();
1269
+ removeEnemy(idx);
1270
+ }
1271
+ }
1272
+ }
1273
+ });
1274
+ } else if (projectile.effect === 'lightning') {
1275
+ createEffect(enemy.x, enemy.y, 'lightning');
1276
+ }
1277
+
1278
+ // Main target damage
1279
+ enemy.health -= projectile.damage;
1280
+
1281
+ // Update health bar
1282
+ const enemyElement = document.querySelectorAll('.enemy')[projectile.enemyIndex];
1283
+ if (enemyElement) {
1284
+ const healthBar = enemyElement.querySelector('.health-bar');
1285
+ healthBar.style.width = `${(enemy.health / enemy.maxHealth) * 100}%`;
1286
+
1287
+ if (enemy.health <= 0) {
1288
+ // Enemy died
1289
+ gameState.money += enemy.reward;
1290
+ updateUI();
1291
+ removeEnemy(projectile.enemyIndex);
1292
+ }
1293
+ }
1294
+
1295
+ removeProjectile(projectileIndex);
1296
+ } else {
1297
+ // Move toward target
1298
+ projectile.x += (dx / distance) * projectile.speed;
1299
+ projectile.y += (dy / distance) * projectile.speed;
1300
+
1301
+ // Update projectile element position
1302
+ const projectileElement = document.querySelectorAll('.projectile')[projectileIndex];
1303
+ if (projectileElement) {
1304
+ projectileElement.style.left = `${projectile.x - 4}px`;
1305
+ projectileElement.style.top = `${projectile.y - 4}px`;
1306
+ }
1307
+ }
1308
+ });
1309
+
1310
+ // Check if wave is complete
1311
+ if (gameState.gameActive && gameState.enemies.length === 0 && !gameState.enemySpawnInterval) {
1312
+ waveComplete();
1313
+ }
1314
+ }
1315
+
1316
+ // Remove enemy
1317
+ function removeEnemy(index) {
1318
+ const enemyElement = document.querySelectorAll('.enemy')[index];
1319
+ if (enemyElement) enemyElement.remove();
1320
+ gameState.enemies.splice(index, 1);
1321
+
1322
+ // Update indices in projectiles
1323
+ gameState.projectiles.forEach(projectile => {
1324
+ if (projectile.enemyIndex > index) {
1325
+ projectile.enemyIndex--;
1326
+ }
1327
+ });
1328
+ }
1329
+
1330
+ // Remove projectile
1331
+ function removeProjectile(index) {
1332
+ const projectileElement = document.querySelectorAll('.projectile')[index];
1333
+ if (projectileElement) projectileElement.remove();
1334
+ gameState.projectiles.splice(index, 1);
1335
+ }
1336
+
1337
+ // Wave complete
1338
+ function waveComplete() {
1339
+ gameState.gameActive = false;
1340
+ gameState.wave++;
1341
+ updateUI();
1342
+ startWaveBtn.disabled = false;
1343
+ restartWaveBtn.disabled = false;
1344
+ }
1345
+
1346
+ // Game over
1347
+ function gameOver() {
1348
+ clearInterval(gameState.gameLoopInterval);
1349
+ clearInterval(gameState.enemySpawnInterval);
1350
+ gameState.gameActive = false;
1351
+
1352
+ finalWaveDisplay.textContent = gameState.wave - 1;
1353
+ gameOverScreen.style.display = 'flex';
1354
+ }
1355
+
1356
+ // Update UI
1357
+ function updateUI() {
1358
+ waveDisplay.textContent = gameState.wave;
1359
+ livesDisplay.textContent = gameState.lives;
1360
+ moneyDisplay.textContent = gameState.money;
1361
+ }
1362
+
1363
+ // Event listeners
1364
+ startWaveBtn.addEventListener('click', startWave);
1365
+ restartWaveBtn.addEventListener('click', restartWave);
1366
+
1367
+ towerShopItems.forEach(item => {
1368
+ item.addEventListener('click', () => {
1369
+ if (gameState.placingTower) {
1370
+ // Cancel previous placement
1371
+ gameState.placingTower = false;
1372
+ gameState.towerType = null;
1373
+
1374
+ // Remove range indicator if it exists
1375
+ const rangeIndicator = document.querySelector('.range-indicator');
1376
+ if (rangeIndicator) rangeIndicator.remove();
1377
+ }
1378
+
1379
+ const type = parseInt(item.dataset.type);
1380
+ const cost = towerTypes[type].cost;
1381
+
1382
+ if (gameState.money >= cost) {
1383
+ gameState.placingTower = true;
1384
+ gameState.towerType = type;
1385
+
1386
+ // Create range indicator
1387
+ const rangeIndicator = document.createElement('div');
1388
+ rangeIndicator.className = 'range-indicator';
1389
+ rangeIndicator.style.width = `${towerTypes[type].range * 2}px`;
1390
+ rangeIndicator.style.height = `${towerTypes[type].range * 2}px`;
1391
+ gameContainer.appendChild(rangeIndicator);
1392
+
1393
+ // Update position on mouse move
1394
+ gameContainer.addEventListener('mousemove', (e) => {
1395
+ if (gameState.placingTower) {
1396
+ const rect = gameContainer.getBoundingClientRect();
1397
+ const x = Math.floor((e.clientX - rect.left) / gameState.cellSize);
1398
+ const y = Math.floor((e.clientY - rect.top) / gameState.cellSize);
1399
+
1400
+ rangeIndicator.style.left = `${x * gameState.cellSize + gameState.cellSize / 2}px`;
1401
+ rangeIndicator.style.top = `${y * gameState.cellSize + gameState.cellSize / 2}px`;
1402
+ }
1403
+ });
1404
+ } else {
1405
+ alert('Not enough money!');
1406
+ }
1407
+ });
1408
+ });
1409
+
1410
+ // Close tower menu when clicking elsewhere
1411
+ document.addEventListener('click', (e) => {
1412
+ if (!towerMenu.contains(e.target) && e.target.className.indexOf('tower') === -1) {
1413
+ towerMenu.style.display = 'none';
1414
+ gameState.selectedTower = null;
1415
+
1416
+ // Remove range indicator
1417
+ const rangeIndicator = document.querySelector('.range-indicator');
1418
+ if (rangeIndicator) rangeIndicator.remove();
1419
+ }
1420
+ });
1421
+
1422
+ upgradeBtn.addEventListener('click', upgradeTower);
1423
+ sellBtn.addEventListener('click', sellTower);
1424
+ restartBtn.addEventListener('click', initGame);
1425
+
1426
+ // Initialize game
1427
+ initGame();
1428
+ </script>
1429
+ <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=jamesjun/modded-tower-defense" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
1430
+ </html>