arad1367 commited on
Commit
9ef4105
·
verified ·
1 Parent(s): edb5f16

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1684 -18
index.html CHANGED
@@ -1,19 +1,1685 @@
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
+ <!-- Q-Learning Simulation By Pejman Ebrahimi -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Q-Learning Simulation by Pejman Ebrahimi</title>
8
+ <style>
9
+ :root {
10
+ --primary: #3f51b5;
11
+ --primary-light: #757de8;
12
+ --primary-dark: #002984;
13
+ --accent: #ff4081;
14
+ --accent-light: #ff79b0;
15
+ --accent-dark: #c60055;
16
+ --success: #4caf50;
17
+ --danger: #f44336;
18
+ --warning: #ff9800;
19
+ --dark: #212121;
20
+ --light: #fafafa;
21
+ --grid-size: 65px;
22
+ --border-radius: 8px;
23
+ --box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1),
24
+ 0 3px 6px rgba(0, 0, 0, 0.15);
25
+ --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
26
+ }
27
+
28
+ * {
29
+ box-sizing: border-box;
30
+ margin: 0;
31
+ padding: 0;
32
+ font-family: Arial, sans-serif;
33
+ }
34
+
35
+ body {
36
+ background-color: var(--light);
37
+ color: var(--dark);
38
+ line-height: 1.6;
39
+ padding: 0;
40
+ display: flex;
41
+ flex-direction: column;
42
+ min-height: 100vh;
43
+ }
44
+
45
+ header {
46
+ background: linear-gradient(
47
+ 135deg,
48
+ var(--primary-dark) 0%,
49
+ var(--primary) 100%
50
+ );
51
+ color: white;
52
+ padding: 2rem 0;
53
+ text-align: center;
54
+ position: relative;
55
+ overflow: hidden;
56
+ box-shadow: var(--box-shadow);
57
+ }
58
+
59
+ .container {
60
+ width: 90%;
61
+ max-width: 1400px;
62
+ margin: 0 auto;
63
+ padding: 2rem 0;
64
+ }
65
+
66
+ .header-content {
67
+ position: relative;
68
+ z-index: 10;
69
+ }
70
+
71
+ h1 {
72
+ font-size: 2.5rem;
73
+ margin: 0;
74
+ font-weight: 300;
75
+ }
76
+
77
+ .subtitle {
78
+ font-weight: 300;
79
+ margin-top: 0.5rem;
80
+ opacity: 0.9;
81
+ }
82
+
83
+ .attribution {
84
+ display: inline-block;
85
+ margin-top: 1rem;
86
+ padding: 0.5rem 1rem;
87
+ background-color: rgba(255, 255, 255, 0.1);
88
+ border-radius: 50px;
89
+ font-weight: 400;
90
+ letter-spacing: 0.5px;
91
+ position: relative;
92
+ z-index: 10;
93
+ }
94
+
95
+ #stars-container {
96
+ position: absolute;
97
+ top: 0;
98
+ left: 0;
99
+ width: 100%;
100
+ height: 100%;
101
+ overflow: hidden;
102
+ z-index: 1;
103
+ }
104
+
105
+ .star {
106
+ position: absolute;
107
+ background-color: #fff;
108
+ width: 3px;
109
+ height: 3px;
110
+ border-radius: 50%;
111
+ opacity: 0;
112
+ animation: starAnimation 2s linear forwards;
113
+ }
114
+
115
+ @keyframes starAnimation {
116
+ 0% {
117
+ transform: translate(0, 0) scale(0);
118
+ opacity: 1;
119
+ }
120
+ 100% {
121
+ transform: translate(var(--tx), var(--ty)) scale(1);
122
+ opacity: 0;
123
+ }
124
+ }
125
+
126
+ main {
127
+ flex: 1;
128
+ padding: 2rem 0;
129
+ }
130
+
131
+ .grid-layout {
132
+ display: grid;
133
+ grid-template-columns: 1fr 1fr;
134
+ grid-gap: 2rem;
135
+ }
136
+
137
+ h2 {
138
+ font-weight: 400;
139
+ color: var(--primary);
140
+ margin-bottom: 1rem;
141
+ border-bottom: 1px solid #eee;
142
+ padding-bottom: 0.5rem;
143
+ }
144
+
145
+ h3 {
146
+ font-weight: 400;
147
+ color: var(--primary-dark);
148
+ margin-bottom: 0.5rem;
149
+ }
150
+
151
+ .card {
152
+ background-color: white;
153
+ border-radius: var(--border-radius);
154
+ box-shadow: var(--box-shadow);
155
+ padding: 1.5rem;
156
+ margin-bottom: 2rem;
157
+ transition: var(--transition);
158
+ }
159
+
160
+ .card:hover {
161
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1),
162
+ 0 6px 12px rgba(0, 0, 0, 0.15);
163
+ }
164
+
165
+ .flex-container {
166
+ display: flex;
167
+ gap: 1.5rem;
168
+ }
169
+
170
+ .world-container {
171
+ flex: 1;
172
+ }
173
+
174
+ .world-wrapper {
175
+ display: flex;
176
+ gap: 2rem;
177
+ justify-content: space-between;
178
+ align-items: flex-start;
179
+ }
180
+
181
+ .grid-container,
182
+ .q-values-container {
183
+ display: grid;
184
+ grid-template-columns: repeat(5, var(--grid-size));
185
+ grid-template-rows: repeat(5, var(--grid-size));
186
+ gap: 5px;
187
+ }
188
+
189
+ .grid-cell {
190
+ position: relative;
191
+ background-color: white;
192
+ border-radius: 4px;
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: center;
196
+ border: 1px solid #e0e0e0;
197
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
198
+ transition: var(--transition);
199
+ }
200
+
201
+ .grid-cell.obstacle {
202
+ background-color: var(--danger);
203
+ color: white;
204
+ }
205
+
206
+ .grid-cell.goal {
207
+ background-color: var(--success);
208
+ color: white;
209
+ }
210
+
211
+ .agent {
212
+ position: absolute;
213
+ width: 26px;
214
+ height: 26px;
215
+ background-color: var(--primary);
216
+ border-radius: 50%;
217
+ z-index: 10;
218
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
219
+ transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
220
+ }
221
+
222
+ .trail {
223
+ position: absolute;
224
+ width: 10px;
225
+ height: 10px;
226
+ background-color: rgba(63, 81, 181, 0.3);
227
+ border-radius: 50%;
228
+ z-index: 5;
229
+ transform: scale(0);
230
+ animation: pulseTrail 1.5s ease-out forwards;
231
+ }
232
+
233
+ @keyframes pulseTrail {
234
+ 0% {
235
+ transform: scale(0);
236
+ opacity: 0.6;
237
+ }
238
+ 100% {
239
+ transform: scale(1);
240
+ opacity: 0;
241
+ }
242
+ }
243
+
244
+ .q-cell {
245
+ background-color: white;
246
+ border-radius: 4px;
247
+ padding: 5px;
248
+ font-size: 0.7rem;
249
+ border: 1px solid #e0e0e0;
250
+ position: relative;
251
+ display: flex;
252
+ justify-content: center;
253
+ align-items: center;
254
+ }
255
+
256
+ .q-arrow {
257
+ position: absolute;
258
+ width: 0;
259
+ height: 0;
260
+ border-style: solid;
261
+ transition: var(--transition);
262
+ }
263
+
264
+ .q-arrow.up {
265
+ top: 2px;
266
+ left: 50%;
267
+ transform: translateX(-50%);
268
+ border-width: 0 6px 8px 6px;
269
+ border-color: transparent transparent rgba(63, 81, 181, var(--opacity))
270
+ transparent;
271
+ }
272
+
273
+ .q-arrow.right {
274
+ top: 50%;
275
+ right: 2px;
276
+ transform: translateY(-50%);
277
+ border-width: 6px 0 6px 8px;
278
+ border-color: transparent transparent transparent
279
+ rgba(63, 81, 181, var(--opacity));
280
+ }
281
+
282
+ .q-arrow.down {
283
+ bottom: 2px;
284
+ left: 50%;
285
+ transform: translateX(-50%);
286
+ border-width: 8px 6px 0 6px;
287
+ border-color: rgba(63, 81, 181, var(--opacity)) transparent transparent
288
+ transparent;
289
+ }
290
+
291
+ .q-arrow.left {
292
+ top: 50%;
293
+ left: 2px;
294
+ transform: translateY(-50%);
295
+ border-width: 6px 8px 6px 0;
296
+ border-color: transparent rgba(63, 81, 181, var(--opacity)) transparent
297
+ transparent;
298
+ }
299
+
300
+ .q-value {
301
+ position: absolute;
302
+ font-size: 9px;
303
+ color: #666;
304
+ transition: var(--transition);
305
+ }
306
+
307
+ .q-value.best {
308
+ color: var(--primary);
309
+ font-weight: 700;
310
+ }
311
+
312
+ .reward-display {
313
+ position: absolute;
314
+ font-size: 14px;
315
+ font-weight: bold;
316
+ z-index: 20;
317
+ opacity: 0;
318
+ animation: fadeUp 1.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
319
+ }
320
+
321
+ @keyframes fadeUp {
322
+ 0% {
323
+ opacity: 1;
324
+ transform: translateY(0);
325
+ }
326
+ 100% {
327
+ opacity: 0;
328
+ transform: translateY(-30px);
329
+ }
330
+ }
331
+
332
+ .positive-reward {
333
+ color: var(--success);
334
+ }
335
+ .negative-reward {
336
+ color: var(--danger);
337
+ }
338
+
339
+ .control-section {
340
+ display: grid;
341
+ grid-template-columns: 1fr 1fr;
342
+ gap: 1.5rem;
343
+ }
344
+
345
+ .control-panel {
346
+ display: grid;
347
+ grid-template-columns: 1fr;
348
+ gap: 1rem;
349
+ }
350
+
351
+ .slider-container {
352
+ margin-bottom: 0.5rem;
353
+ }
354
+
355
+ .slider-label {
356
+ display: flex;
357
+ justify-content: space-between;
358
+ margin-bottom: 0.25rem;
359
+ }
360
+
361
+ .slider-label .value {
362
+ font-weight: 500;
363
+ color: var(--primary);
364
+ }
365
+
366
+ input[type="range"] {
367
+ width: 100%;
368
+ height: 6px;
369
+ background: #e0e0e0;
370
+ border-radius: 3px;
371
+ appearance: none;
372
+ margin: 0.5rem 0;
373
+ }
374
+
375
+ input[type="range"]::-webkit-slider-thumb {
376
+ appearance: none;
377
+ width: 16px;
378
+ height: 16px;
379
+ background: var(--primary);
380
+ border-radius: 50%;
381
+ cursor: pointer;
382
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
383
+ }
384
+
385
+ input[type="range"]::-moz-range-thumb {
386
+ width: 16px;
387
+ height: 16px;
388
+ background: var(--primary);
389
+ border-radius: 50%;
390
+ border: none;
391
+ cursor: pointer;
392
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
393
+ }
394
+
395
+ input[type="range"]::-ms-thumb {
396
+ width: 16px;
397
+ height: 16px;
398
+ background: var(--primary);
399
+ border-radius: 50%;
400
+ cursor: pointer;
401
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
402
+ }
403
+
404
+ .slider-description {
405
+ font-size: 0.8rem;
406
+ color: #666;
407
+ margin-top: 0.25rem;
408
+ }
409
+
410
+ .mode-selector {
411
+ display: flex;
412
+ gap: 0.5rem;
413
+ margin: 1rem 0;
414
+ }
415
+
416
+ .mode-btn {
417
+ flex: 1;
418
+ padding: 0.75rem;
419
+ text-align: center;
420
+ background-color: #f5f5f5;
421
+ border-radius: 4px;
422
+ cursor: pointer;
423
+ transition: var(--transition);
424
+ font-weight: 500;
425
+ }
426
+
427
+ .mode-btn:hover {
428
+ background-color: #e0e0e0;
429
+ }
430
+
431
+ .mode-btn.active {
432
+ background-color: var(--primary);
433
+ color: white;
434
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
435
+ }
436
+
437
+ .btn-container {
438
+ display: flex;
439
+ gap: 0.5rem;
440
+ margin-top: 1rem;
441
+ }
442
+
443
+ button {
444
+ padding: 0.75rem 1.25rem;
445
+ background-color: var(--primary);
446
+ color: white;
447
+ border: none;
448
+ border-radius: 4px;
449
+ cursor: pointer;
450
+ transition: var(--transition);
451
+ font-weight: 500;
452
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
453
+ flex: 1;
454
+ }
455
+
456
+ button:hover {
457
+ background-color: var(--primary-light);
458
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
459
+ }
460
+
461
+ button:active {
462
+ transform: translateY(1px);
463
+ }
464
+
465
+ button.secondary {
466
+ background-color: #e0e0e0;
467
+ color: #333;
468
+ }
469
+
470
+ button.secondary:hover {
471
+ background-color: #d0d0d0;
472
+ }
473
+
474
+ .stats-card {
475
+ display: grid;
476
+ grid-template-columns: repeat(4, 1fr);
477
+ gap: 1rem;
478
+ margin-bottom: 1.5rem;
479
+ }
480
+
481
+ .stat-box {
482
+ background-color: #f5f5f5;
483
+ padding: 1rem;
484
+ border-radius: 8px;
485
+ text-align: center;
486
+ transition: var(--transition);
487
+ }
488
+
489
+ .stat-box:hover {
490
+ transform: translateY(-2px);
491
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
492
+ }
493
+
494
+ .stat-label {
495
+ font-size: 0.8rem;
496
+ color: #666;
497
+ }
498
+
499
+ .stat-value {
500
+ font-size: 1.5rem;
501
+ font-weight: 500;
502
+ color: var(--primary);
503
+ margin-top: 0.25rem;
504
+ }
505
+
506
+ .chart-container {
507
+ height: 250px;
508
+ margin: 1.5rem 0;
509
+ background-color: #f5f5f5;
510
+ border-radius: 8px;
511
+ padding: 1rem;
512
+ position: relative;
513
+ }
514
+
515
+ .simple-chart {
516
+ width: 100%;
517
+ height: 100%;
518
+ position: relative;
519
+ }
520
+
521
+ .chart-bar {
522
+ position: absolute;
523
+ bottom: 0;
524
+ background-color: var(--primary);
525
+ border-radius: 4px 4px 0 0;
526
+ transition: height 0.3s ease;
527
+ }
528
+
529
+ .chart-line {
530
+ position: absolute;
531
+ bottom: 50%;
532
+ left: 0;
533
+ width: 100%;
534
+ height: 1px;
535
+ background-color: rgba(0, 0, 0, 0.1);
536
+ }
537
+
538
+ .chart-label {
539
+ position: absolute;
540
+ bottom: -20px;
541
+ font-size: 10px;
542
+ text-align: center;
543
+ transform: translateX(-50%);
544
+ color: #666;
545
+ }
546
+
547
+ .chart-legend {
548
+ position: absolute;
549
+ top: 10px;
550
+ right: 10px;
551
+ display: flex;
552
+ gap: 10px;
553
+ }
554
+
555
+ .legend-item {
556
+ display: flex;
557
+ align-items: center;
558
+ gap: 5px;
559
+ font-size: 12px;
560
+ }
561
+
562
+ .legend-color {
563
+ width: 12px;
564
+ height: 12px;
565
+ border-radius: 2px;
566
+ }
567
+
568
+ .leaderboard {
569
+ width: 100%;
570
+ border-collapse: collapse;
571
+ }
572
+
573
+ .leaderboard th {
574
+ text-align: left;
575
+ padding: 0.75rem;
576
+ background-color: #f5f5f5;
577
+ font-weight: 500;
578
+ }
579
+
580
+ .leaderboard td {
581
+ padding: 0.75rem;
582
+ border-bottom: 1px solid #eee;
583
+ }
584
+
585
+ .leaderboard tr:last-child td {
586
+ border-bottom: none;
587
+ }
588
+
589
+ .notification {
590
+ position: fixed;
591
+ top: 20px;
592
+ right: 20px;
593
+ background-color: var(--primary);
594
+ color: white;
595
+ padding: 1rem 1.5rem;
596
+ border-radius: var(--border-radius);
597
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
598
+ z-index: 1000;
599
+ opacity: 0;
600
+ transform: translateX(50px);
601
+ transition: var(--transition);
602
+ max-width: 300px;
603
+ }
604
+
605
+ .notification.show {
606
+ opacity: 1;
607
+ transform: translateX(0);
608
+ }
609
+
610
+ .notification-title {
611
+ font-weight: 500;
612
+ margin-bottom: 0.5rem;
613
+ display: flex;
614
+ align-items: center;
615
+ gap: 8px;
616
+ }
617
+
618
+ .notification-body {
619
+ font-size: 0.9rem;
620
+ opacity: 0.9;
621
+ }
622
+
623
+ .legend {
624
+ display: flex;
625
+ flex-wrap: wrap;
626
+ gap: 1rem;
627
+ margin-top: 1rem;
628
+ padding: 0.5rem;
629
+ background-color: #f5f5f5;
630
+ border-radius: 4px;
631
+ }
632
+
633
+ .legend-item {
634
+ display: flex;
635
+ align-items: center;
636
+ gap: 0.5rem;
637
+ font-size: 0.9rem;
638
+ }
639
+
640
+ .legend-color {
641
+ width: 12px;
642
+ height: 12px;
643
+ border-radius: 3px;
644
+ }
645
+
646
+ footer {
647
+ text-align: center;
648
+ padding: 1.5rem;
649
+ background-color: var(--primary-dark);
650
+ color: white;
651
+ margin-top: 2rem;
652
+ }
653
+
654
+ @media (max-width: 1200px) {
655
+ .grid-layout {
656
+ grid-template-columns: 1fr;
657
+ }
658
+
659
+ .world-wrapper {
660
+ flex-direction: column;
661
+ align-items: center;
662
+ }
663
+
664
+ .grid-container,
665
+ .q-values-container {
666
+ margin: 0 auto;
667
+ }
668
+
669
+ .control-section {
670
+ grid-template-columns: 1fr;
671
+ }
672
+
673
+ .stats-card {
674
+ grid-template-columns: repeat(2, 1fr);
675
+ }
676
+ }
677
+
678
+ @media (max-width: 768px) {
679
+ :root {
680
+ --grid-size: 50px;
681
+ }
682
+
683
+ .container {
684
+ width: 95%;
685
+ padding: 1rem 0;
686
+ }
687
+
688
+ h1 {
689
+ font-size: 1.8rem;
690
+ }
691
+
692
+ .control-panel {
693
+ grid-template-columns: 1fr;
694
+ }
695
+ }
696
+
697
+ /* Loading spinner for initial setup */
698
+ .loader {
699
+ border: 4px solid rgba(63, 81, 181, 0.1);
700
+ border-top: 4px solid var(--primary);
701
+ border-radius: 50%;
702
+ width: 40px;
703
+ height: 40px;
704
+ animation: spin 1s linear infinite;
705
+ margin: 2rem auto;
706
+ }
707
+
708
+ @keyframes spin {
709
+ 0% {
710
+ transform: rotate(0deg);
711
+ }
712
+ 100% {
713
+ transform: rotate(360deg);
714
+ }
715
+ }
716
+
717
+ /* Exploration mode visual */
718
+ .explore-indicator {
719
+ position: absolute;
720
+ width: 100%;
721
+ height: 100%;
722
+ background-color: rgba(255, 152, 0, 0.2);
723
+ border-radius: 4px;
724
+ z-index: 5;
725
+ display: flex;
726
+ align-items: center;
727
+ justify-content: center;
728
+ }
729
+ </style>
730
+ </head>
731
+ <body>
732
+ <header>
733
+ <div id="stars-container"></div>
734
+ <div class="header-content">
735
+ <h1>Q-Learning Simulation</h1>
736
+ <p class="subtitle">Reinforcement Learning in Action</p>
737
+ <div class="attribution">Designed by Pejman Ebrahimi</div>
738
+ </div>
739
+ </header>
740
+
741
+ <main>
742
+ <div class="container">
743
+ <div class="loader" id="loader"></div>
744
+
745
+ <div class="grid-layout" id="main-content" style="display: none">
746
+ <div class="left-section">
747
+ <div class="card">
748
+ <h2>Interactive Environment</h2>
749
+ <div class="world-wrapper">
750
+ <div class="world-container">
751
+ <h3>Grid World</h3>
752
+ <div class="grid-container" id="grid-container"></div>
753
+ </div>
754
+
755
+ <div class="world-container">
756
+ <h3>Q-Values</h3>
757
+ <div class="q-values-container" id="q-values-container"></div>
758
+ </div>
759
+ </div>
760
+
761
+ <div class="legend">
762
+ <div class="legend-item">
763
+ <div
764
+ class="legend-color"
765
+ style="background-color: var(--primary)"
766
+ ></div>
767
+ <span>Agent</span>
768
+ </div>
769
+ <div class="legend-item">
770
+ <div
771
+ class="legend-color"
772
+ style="background-color: var(--success)"
773
+ ></div>
774
+ <span>Goal (+10)</span>
775
+ </div>
776
+ <div class="legend-item">
777
+ <div
778
+ class="legend-color"
779
+ style="background-color: var(--danger)"
780
+ ></div>
781
+ <span>Obstacle (-5)</span>
782
+ </div>
783
+ <div class="legend-item">
784
+ <div
785
+ class="legend-color"
786
+ style="background-color: rgba(63, 81, 181, 0.3)"
787
+ ></div>
788
+ <span>Path History</span>
789
+ </div>
790
+ </div>
791
+ </div>
792
+
793
+ <div class="card">
794
+ <h2>Learning Parameters</h2>
795
+ <div class="control-section">
796
+ <div class="control-panel">
797
+ <div class="slider-container">
798
+ <div class="slider-label">
799
+ <span>Learning Rate (α)</span>
800
+ <span class="value" id="learning-rate-value">0.1</span>
801
+ </div>
802
+ <input
803
+ type="range"
804
+ id="learning-rate"
805
+ min="0.01"
806
+ max="1"
807
+ step="0.01"
808
+ value="0.1"
809
+ />
810
+ <div class="slider-description">
811
+ How quickly the agent incorporates new information
812
+ </div>
813
+ </div>
814
+
815
+ <div class="slider-container">
816
+ <div class="slider-label">
817
+ <span>Discount Factor (γ)</span>
818
+ <span class="value" id="discount-factor-value">0.9</span>
819
+ </div>
820
+ <input
821
+ type="range"
822
+ id="discount-factor"
823
+ min="0"
824
+ max="0.99"
825
+ step="0.01"
826
+ value="0.9"
827
+ />
828
+ <div class="slider-description">
829
+ How much future rewards matter
830
+ </div>
831
+ </div>
832
+ </div>
833
+
834
+ <div class="control-panel">
835
+ <div class="slider-container">
836
+ <div class="slider-label">
837
+ <span>Exploration Rate (ε)</span>
838
+ <span class="value" id="exploration-rate-value">0.3</span>
839
+ </div>
840
+ <input
841
+ type="range"
842
+ id="exploration-rate"
843
+ min="0"
844
+ max="1"
845
+ step="0.01"
846
+ value="0.3"
847
+ />
848
+ <div class="slider-description">
849
+ Chance of taking random actions
850
+ </div>
851
+ </div>
852
+
853
+ <div class="slider-container">
854
+ <div class="slider-label">
855
+ <span>Animation Speed</span>
856
+ <span class="value" id="animation-speed-value"
857
+ >300ms</span
858
+ >
859
+ </div>
860
+ <input
861
+ type="range"
862
+ id="animation-speed"
863
+ min="50"
864
+ max="1000"
865
+ step="50"
866
+ value="300"
867
+ />
868
+ <div class="slider-description">
869
+ How quickly the simulation runs
870
+ </div>
871
+ </div>
872
+ </div>
873
+ </div>
874
+
875
+ <div class="mode-selector">
876
+ <div class="mode-btn active" id="learning-mode">
877
+ Learning Mode
878
+ </div>
879
+ <div class="mode-btn" id="optimal-mode">
880
+ Optimal Policy Mode
881
+ </div>
882
+ </div>
883
+
884
+ <div class="btn-container">
885
+ <button id="start-button">Start Learning</button>
886
+ <button id="step-button">Single Step</button>
887
+ <button id="reset-button" class="secondary">Reset</button>
888
+ </div>
889
+ </div>
890
+ </div>
891
+
892
+ <div class="right-section">
893
+ <div class="card">
894
+ <h2>Performance Metrics</h2>
895
+ <div class="stats-card">
896
+ <div class="stat-box">
897
+ <div class="stat-label">Episodes</div>
898
+ <div class="stat-value" id="episode-count">0</div>
899
+ </div>
900
+ <div class="stat-box">
901
+ <div class="stat-label">Current Steps</div>
902
+ <div class="stat-value" id="step-count">0</div>
903
+ </div>
904
+ <div class="stat-box">
905
+ <div class="stat-label">Success Rate</div>
906
+ <div class="stat-value" id="success-rate">0%</div>
907
+ </div>
908
+ <div class="stat-box">
909
+ <div class="stat-label">Avg. Steps</div>
910
+ <div class="stat-value" id="avg-completion">0</div>
911
+ </div>
912
+ </div>
913
+
914
+ <h3>Learning Progress</h3>
915
+ <div class="chart-container">
916
+ <div class="simple-chart" id="steps-chart"></div>
917
+ <div class="chart-legend">
918
+ <div class="legend-item">
919
+ <div
920
+ class="legend-color"
921
+ style="background-color: var(--primary)"
922
+ ></div>
923
+ <span>Steps per Episode</span>
924
+ </div>
925
+ </div>
926
+ </div>
927
+ </div>
928
+
929
+ <div class="card">
930
+ <h2>Leaderboard - Best Paths</h2>
931
+ <table class="leaderboard">
932
+ <thead>
933
+ <tr>
934
+ <th>Rank</th>
935
+ <th>Episode</th>
936
+ <th>Steps</th>
937
+ <th>Reward</th>
938
+ </tr>
939
+ </thead>
940
+ <tbody id="leaderboard-body">
941
+ <tr>
942
+ <td colspan="4" style="text-align: center">
943
+ No successful episodes yet
944
+ </td>
945
+ </tr>
946
+ </tbody>
947
+ </table>
948
+ </div>
949
+ </div>
950
+ </div>
951
+ </div>
952
+ </main>
953
+
954
+ <footer>
955
+ <p>© 2025 Pejman Ebrahimi • Interactive Q-Learning Simulation</p>
956
+ </footer>
957
+
958
+ <div class="notification" id="notification">
959
+ <div class="notification-title">
960
+ <span>🏆</span>
961
+ <span id="notification-title">New Record!</span>
962
+ </div>
963
+ <div class="notification-body" id="notification-message"></div>
964
+ </div>
965
+
966
+ <script>
967
+ // Environment setup
968
+ const gridSize = 5;
969
+ const numStates = gridSize * gridSize;
970
+ const numActions = 4; // Up, Right, Down, Left
971
+ const obstacles = [7, 8, 17]; // Obstacle positions
972
+ const goal = 24; // Goal position
973
+ let agentPos = 0; // Start position
974
+
975
+ // Rewards
976
+ const stepReward = -0.1; // Penalty for each step
977
+ const obstacleReward = -5; // Penalty for hitting obstacle
978
+ const goalReward = 10; // Reward for reaching goal
979
+
980
+ // Learning state
981
+ let qTable = Array(numStates)
982
+ .fill()
983
+ .map(() => Array(numActions).fill(0));
984
+ let isLearning = false;
985
+ let isSingleStep = false;
986
+ let episodes = 0;
987
+ let steps = 0;
988
+ let episodeSteps = 0;
989
+ let stepsHistory = [];
990
+ let rewardsHistory = [];
991
+ let successCount = 0;
992
+ let totalCompletionSteps = 0;
993
+ let bestPathSteps = Infinity;
994
+ let leaderboard = [];
995
+ let currentEpisodeReward = 0;
996
+ let isOptimalMode = false;
997
+ let lastActionWasExploration = false;
998
+
999
+ // Agent parameters
1000
+ let learningRate = 0.1;
1001
+ let discountFactor = 0.9;
1002
+ let explorationRate = 0.3;
1003
+ let animationSpeed = 300;
1004
+
1005
+ // DOM Elements
1006
+ const loader = document.getElementById("loader");
1007
+ const mainContent = document.getElementById("main-content");
1008
+ const gridContainer = document.getElementById("grid-container");
1009
+ const qValuesContainer = document.getElementById("q-values-container");
1010
+ const startButton = document.getElementById("start-button");
1011
+ const stepButton = document.getElementById("step-button");
1012
+ const resetButton = document.getElementById("reset-button");
1013
+ const learningModeBtn = document.getElementById("learning-mode");
1014
+ const optimalModeBtn = document.getElementById("optimal-mode");
1015
+ const episodeCountEl = document.getElementById("episode-count");
1016
+ const stepCountEl = document.getElementById("step-count");
1017
+ const successRateEl = document.getElementById("success-rate");
1018
+ const avgCompletionEl = document.getElementById("avg-completion");
1019
+ const leaderboardBody = document.getElementById("leaderboard-body");
1020
+ const notification = document.getElementById("notification");
1021
+ const stepsChart = document.getElementById("steps-chart");
1022
+
1023
+ // Parameter sliders
1024
+ const learningRateSlider = document.getElementById("learning-rate");
1025
+ const learningRateValue = document.getElementById("learning-rate-value");
1026
+ const discountFactorSlider = document.getElementById("discount-factor");
1027
+ const discountFactorValue = document.getElementById(
1028
+ "discount-factor-value"
1029
+ );
1030
+ const explorationRateSlider = document.getElementById("exploration-rate");
1031
+ const explorationRateValue = document.getElementById(
1032
+ "exploration-rate-value"
1033
+ );
1034
+ const animationSpeedSlider = document.getElementById("animation-speed");
1035
+ const animationSpeedValue = document.getElementById(
1036
+ "animation-speed-value"
1037
+ );
1038
+
1039
+ // Update parameters from sliders
1040
+ learningRateSlider.addEventListener("input", function () {
1041
+ learningRate = parseFloat(this.value);
1042
+ learningRateValue.textContent = learningRate.toFixed(2);
1043
+ });
1044
+
1045
+ discountFactorSlider.addEventListener("input", function () {
1046
+ discountFactor = parseFloat(this.value);
1047
+ discountFactorValue.textContent = discountFactor.toFixed(2);
1048
+ });
1049
+
1050
+ explorationRateSlider.addEventListener("input", function () {
1051
+ explorationRate = parseFloat(this.value);
1052
+ explorationRateValue.textContent = explorationRate.toFixed(2);
1053
+ });
1054
+
1055
+ animationSpeedSlider.addEventListener("input", function () {
1056
+ animationSpeed = parseInt(this.value);
1057
+ animationSpeedValue.textContent = animationSpeed + "ms";
1058
+ });
1059
+
1060
+ // Mode selection
1061
+ learningModeBtn.addEventListener("click", function () {
1062
+ if (isOptimalMode) {
1063
+ isOptimalMode = false;
1064
+ learningModeBtn.classList.add("active");
1065
+ optimalModeBtn.classList.remove("active");
1066
+ }
1067
+ });
1068
+
1069
+ optimalModeBtn.addEventListener("click", function () {
1070
+ if (!isOptimalMode) {
1071
+ isOptimalMode = true;
1072
+ optimalModeBtn.classList.add("active");
1073
+ learningModeBtn.classList.remove("active");
1074
+ }
1075
+ });
1076
+
1077
+ // Create star animation for header
1078
+ function createStars() {
1079
+ const starsContainer = document.getElementById("stars-container");
1080
+ const numStars = 40;
1081
+
1082
+ for (let i = 0; i < numStars; i++) {
1083
+ setTimeout(() => {
1084
+ const star = document.createElement("div");
1085
+ star.className = "star";
1086
+
1087
+ // Random position
1088
+ const x = Math.random() * 100;
1089
+ const y = Math.random() * 100;
1090
+ star.style.left = `${x}%`;
1091
+ star.style.top = `${y}%`;
1092
+
1093
+ // Random size
1094
+ const size = Math.random() * 4 + 1;
1095
+ star.style.width = `${size}px`;
1096
+ star.style.height = `${size}px`;
1097
+
1098
+ // Random direction
1099
+ const tx = (Math.random() - 0.5) * 200;
1100
+ const ty = (Math.random() - 0.5) * 200;
1101
+ star.style.setProperty("--tx", `${tx}px`);
1102
+ star.style.setProperty("--ty", `${ty}px`);
1103
+
1104
+ starsContainer.appendChild(star);
1105
+
1106
+ // Remove after animation
1107
+ setTimeout(() => {
1108
+ if (star) star.remove();
1109
+ }, 2000);
1110
+ }, i * 50);
1111
+ }
1112
+
1113
+ // Repeat the animation
1114
+ setTimeout(createStars, 4000);
1115
+ }
1116
+
1117
+ // Initialize environment
1118
+ function initializeGrid() {
1119
+ gridContainer.innerHTML = "";
1120
+ qValuesContainer.innerHTML = "";
1121
+
1122
+ // Create grid cells and q-value cells
1123
+ for (let i = 0; i < numStates; i++) {
1124
+ // Grid cell
1125
+ const cell = document.createElement("div");
1126
+ cell.className = "grid-cell";
1127
+ cell.dataset.index = i;
1128
+
1129
+ if (obstacles.includes(i)) {
1130
+ cell.classList.add("obstacle");
1131
+ cell.textContent = "🚫";
1132
+ } else if (i === goal) {
1133
+ cell.classList.add("goal");
1134
+ cell.textContent = "🏆";
1135
+ }
1136
+
1137
+ gridContainer.appendChild(cell);
1138
+
1139
+ // Q-value cell
1140
+ const qCell = document.createElement("div");
1141
+ qCell.className = "q-cell";
1142
+ qCell.dataset.index = i;
1143
+
1144
+ // Add arrows for each action
1145
+ const directions = ["up", "right", "down", "left"];
1146
+ for (let j = 0; j < numActions; j++) {
1147
+ const arrow = document.createElement("div");
1148
+ arrow.className = `q-arrow ${directions[j]}`;
1149
+ arrow.dataset.action = j;
1150
+ arrow.style.setProperty("--opacity", "0.2");
1151
+ qCell.appendChild(arrow);
1152
+
1153
+ const value = document.createElement("div");
1154
+ value.className = "q-value";
1155
+ value.dataset.action = j;
1156
+ value.textContent = "0.00";
1157
+ qCell.appendChild(value);
1158
+ }
1159
+
1160
+ // Position the q-values within the cell
1161
+ const values = qCell.querySelectorAll(".q-value");
1162
+ values[0].style.position = "absolute";
1163
+ values[0].style.top = "5px";
1164
+ values[0].style.left = "50%";
1165
+ values[0].style.transform = "translateX(-50%)";
1166
+
1167
+ values[1].style.position = "absolute";
1168
+ values[1].style.top = "50%";
1169
+ values[1].style.right = "5px";
1170
+ values[1].style.transform = "translateY(-50%)";
1171
+
1172
+ values[2].style.position = "absolute";
1173
+ values[2].style.bottom = "5px";
1174
+ values[2].style.left = "50%";
1175
+ values[2].style.transform = "translateX(-50%)";
1176
+
1177
+ values[3].style.position = "absolute";
1178
+ values[3].style.top = "50%";
1179
+ values[3].style.left = "5px";
1180
+ values[3].style.transform = "translateY(-50%)";
1181
+
1182
+ qValuesContainer.appendChild(qCell);
1183
+ }
1184
+
1185
+ // Add agent
1186
+ const startCell = document.querySelector(
1187
+ `.grid-cell[data-index="${agentPos}"]`
1188
+ );
1189
+ const agent = document.createElement("div");
1190
+ agent.className = "agent";
1191
+ agent.id = "agent";
1192
+ startCell.appendChild(agent);
1193
+ }
1194
+
1195
+ // Create a simple chart without external libraries
1196
+ function updateSimpleChart() {
1197
+ if (stepsHistory.length === 0) return;
1198
+
1199
+ // Clear chart
1200
+ stepsChart.innerHTML = "";
1201
+
1202
+ // Find max value for scaling
1203
+ const maxSteps = Math.max(...stepsHistory);
1204
+ const chartWidth = stepsChart.clientWidth;
1205
+ const chartHeight = stepsChart.clientHeight;
1206
+ const barWidth = Math.max(5, chartWidth / stepsHistory.length - 4);
1207
+
1208
+ // Create bars
1209
+ stepsHistory.forEach((steps, i) => {
1210
+ // Calculate height percentage
1211
+ const heightPercent = steps / maxSteps;
1212
+ const barHeight = heightPercent * chartHeight * 0.8;
1213
+
1214
+ // Create bar
1215
+ const bar = document.createElement("div");
1216
+ bar.className = "chart-bar";
1217
+ bar.style.height = `${barHeight}px`;
1218
+ bar.style.width = `${barWidth}px`;
1219
+ bar.style.left = `${i * (chartWidth / stepsHistory.length)}px`;
1220
+
1221
+ // Add tooltip
1222
+ bar.title = `Episode ${i + 1}: ${steps} steps`;
1223
+
1224
+ // Add to chart
1225
+ stepsChart.appendChild(bar);
1226
+
1227
+ // Add label every 5 episodes
1228
+ if ((i + 1) % 5 === 0 || i === 0) {
1229
+ const label = document.createElement("div");
1230
+ label.className = "chart-label";
1231
+ label.textContent = i + 1;
1232
+ label.style.left = `${
1233
+ i * (chartWidth / stepsHistory.length) + barWidth / 2
1234
+ }px`;
1235
+ stepsChart.appendChild(label);
1236
+ }
1237
+ });
1238
+
1239
+ // Add mid line
1240
+ const midLine = document.createElement("div");
1241
+ midLine.className = "chart-line";
1242
+ stepsChart.appendChild(midLine);
1243
+ }
1244
+
1245
+ // Show notification
1246
+ function showNotification(title, message, duration = 3000) {
1247
+ document.getElementById("notification-title").textContent = title;
1248
+ document.getElementById("notification-message").textContent = message;
1249
+ notification.classList.add("show");
1250
+
1251
+ setTimeout(() => {
1252
+ notification.classList.remove("show");
1253
+ }, duration);
1254
+ }
1255
+
1256
+ // Update leaderboard
1257
+ function updateLeaderboard() {
1258
+ // Sort leaderboard by steps (ascending)
1259
+ leaderboard.sort((a, b) => a.steps - b.steps);
1260
+
1261
+ // Keep only top 5
1262
+ if (leaderboard.length > 5) {
1263
+ leaderboard = leaderboard.slice(0, 5);
1264
+ }
1265
+
1266
+ // Update display
1267
+ leaderboardBody.innerHTML = "";
1268
+
1269
+ if (leaderboard.length === 0) {
1270
+ const row = document.createElement("tr");
1271
+ const cell = document.createElement("td");
1272
+ cell.colSpan = 4;
1273
+ cell.style.textAlign = "center";
1274
+ cell.textContent = "No successful episodes yet";
1275
+ row.appendChild(cell);
1276
+ leaderboardBody.appendChild(row);
1277
+ } else {
1278
+ leaderboard.forEach((entry, index) => {
1279
+ const row = document.createElement("tr");
1280
+
1281
+ const rankCell = document.createElement("td");
1282
+ rankCell.textContent = index + 1;
1283
+ row.appendChild(rankCell);
1284
+
1285
+ const episodeCell = document.createElement("td");
1286
+ episodeCell.textContent = entry.episode;
1287
+ row.appendChild(episodeCell);
1288
+
1289
+ const stepsCell = document.createElement("td");
1290
+ stepsCell.textContent = entry.steps;
1291
+ row.appendChild(stepsCell);
1292
+
1293
+ const rewardCell = document.createElement("td");
1294
+ rewardCell.textContent = entry.reward.toFixed(1);
1295
+ row.appendChild(rewardCell);
1296
+
1297
+ leaderboardBody.appendChild(row);
1298
+ });
1299
+ }
1300
+ }
1301
+
1302
+ // Update Q-value visualization
1303
+ function updateQValues() {
1304
+ for (let i = 0; i < numStates; i++) {
1305
+ const qCell = qValuesContainer.querySelector(
1306
+ `.q-cell[data-index="${i}"]`
1307
+ );
1308
+
1309
+ // Skip obstacles and goal
1310
+ if (obstacles.includes(i) || i === goal) continue;
1311
+
1312
+ // Find max Q-value for this state
1313
+ const maxQ = Math.max(...qTable[i]);
1314
+ const bestAction = qTable[i].indexOf(maxQ);
1315
+
1316
+ // Update each action's display
1317
+ for (let j = 0; j < numActions; j++) {
1318
+ const qValue = qTable[i][j];
1319
+ const valueEl = qCell.querySelector(`.q-value[data-action="${j}"]`);
1320
+ const arrowEl = qCell.querySelector(`.q-arrow[data-action="${j}"]`);
1321
+
1322
+ // Update value text
1323
+ valueEl.textContent = qValue.toFixed(2);
1324
+
1325
+ // Update styling for best action
1326
+ if (j === bestAction && maxQ > 0) {
1327
+ valueEl.classList.add("best");
1328
+ } else {
1329
+ valueEl.classList.remove("best");
1330
+ }
1331
+
1332
+ // Update arrow opacity based on value
1333
+ const opacity =
1334
+ qValue <= 0 ? 0.1 : Math.min(0.2 + (qValue / 10) * 0.8, 1);
1335
+ arrowEl.style.setProperty("--opacity", opacity);
1336
+ }
1337
+ }
1338
+ }
1339
+
1340
+ // Get action based on Q-values and exploration rate
1341
+ function getAction(state) {
1342
+ // In optimal mode, always choose best action
1343
+ if (isOptimalMode) {
1344
+ return qTable[state].indexOf(Math.max(...qTable[state]));
1345
+ }
1346
+
1347
+ // Exploration (random action)
1348
+ if (Math.random() < explorationRate) {
1349
+ lastActionWasExploration = true;
1350
+ return Math.floor(Math.random() * numActions);
1351
+ }
1352
+
1353
+ // Exploitation (best action)
1354
+ lastActionWasExploration = false;
1355
+ return qTable[state].indexOf(Math.max(...qTable[state]));
1356
+ }
1357
+
1358
+ // Get next state based on current state and action
1359
+ function getNextState(state, action) {
1360
+ let row = Math.floor(state / gridSize);
1361
+ let col = state % gridSize;
1362
+ let newRow = row;
1363
+ let newCol = col;
1364
+
1365
+ // Move according to action (0=Up, 1=Right, 2=Down, 3=Left)
1366
+ switch (action) {
1367
+ case 0:
1368
+ newRow = Math.max(0, row - 1);
1369
+ break;
1370
+ case 1:
1371
+ newCol = Math.min(gridSize - 1, col + 1);
1372
+ break;
1373
+ case 2:
1374
+ newRow = Math.min(gridSize - 1, row + 1);
1375
+ break;
1376
+ case 3:
1377
+ newCol = Math.max(0, col - 1);
1378
+ break;
1379
+ }
1380
+
1381
+ return newRow * gridSize + newCol;
1382
+ }
1383
+
1384
+ // Get reward for a given state
1385
+ function getReward(state) {
1386
+ if (state === goal) return goalReward;
1387
+ if (obstacles.includes(state)) return obstacleReward;
1388
+ return stepReward;
1389
+ }
1390
+
1391
+ // Check if episode is done
1392
+ function isDone(state) {
1393
+ return state === goal; // Only goal state ends episode - obstacles don't terminate episode
1394
+ }
1395
+
1396
+ // Move agent
1397
+ function moveAgent(newPos) {
1398
+ const agent = document.getElementById("agent");
1399
+ if (agent) agent.remove();
1400
+
1401
+ const cell = document.querySelector(
1402
+ `.grid-cell[data-index="${newPos}"]`
1403
+ );
1404
+ const newAgent = document.createElement("div");
1405
+ newAgent.className = "agent";
1406
+ newAgent.id = "agent";
1407
+ cell.appendChild(newAgent);
1408
+
1409
+ // Add trail effect
1410
+ const oldCell = document.querySelector(
1411
+ `.grid-cell[data-index="${agentPos}"]`
1412
+ );
1413
+ if (oldCell && agentPos !== newPos) {
1414
+ const trail = document.createElement("div");
1415
+ trail.className = "trail";
1416
+ oldCell.appendChild(trail);
1417
+
1418
+ // Remove trail after a delay
1419
+ setTimeout(() => {
1420
+ if (trail) trail.remove();
1421
+ }, 2000);
1422
+ }
1423
+
1424
+ // Show exploration indicator
1425
+ if (lastActionWasExploration) {
1426
+ const exploreIndicator = document.createElement("div");
1427
+ exploreIndicator.className = "explore-indicator";
1428
+ exploreIndicator.innerHTML = "🔍";
1429
+ cell.appendChild(exploreIndicator);
1430
+
1431
+ setTimeout(() => {
1432
+ if (exploreIndicator) exploreIndicator.remove();
1433
+ }, animationSpeed * 0.8);
1434
+ }
1435
+
1436
+ agentPos = newPos;
1437
+ }
1438
+
1439
+ // Display reward
1440
+ function displayReward(reward, pos) {
1441
+ const cell = document.querySelector(`.grid-cell[data-index="${pos}"]`);
1442
+ const display = document.createElement("div");
1443
+ display.className = "reward-display";
1444
+
1445
+ if (reward > 0) {
1446
+ display.classList.add("positive-reward");
1447
+ display.textContent = `+${reward}`;
1448
+ } else {
1449
+ display.classList.add("negative-reward");
1450
+ display.textContent = reward;
1451
+ }
1452
+
1453
+ cell.appendChild(display);
1454
+
1455
+ // Remove after animation completes
1456
+ setTimeout(() => {
1457
+ if (display) display.remove();
1458
+ }, 1500);
1459
+ }
1460
+
1461
+ // Update statistics
1462
+ function updateStats() {
1463
+ episodeCountEl.textContent = episodes;
1464
+ stepCountEl.textContent = episodeSteps;
1465
+
1466
+ // Success rate
1467
+ if (episodes > 0) {
1468
+ const rate = Math.round((successCount / episodes) * 100);
1469
+ successRateEl.textContent = `${rate}%`;
1470
+ }
1471
+
1472
+ // Average completion steps
1473
+ if (successCount > 0) {
1474
+ const avg = Math.round(totalCompletionSteps / successCount);
1475
+ avgCompletionEl.textContent = avg;
1476
+ }
1477
+ }
1478
+
1479
+ // Take a step in the environment
1480
+ function step() {
1481
+ if (!isLearning && !isSingleStep) return;
1482
+
1483
+ // Choose action
1484
+ const action = getAction(agentPos);
1485
+
1486
+ // Get new state and reward
1487
+ const newState = getNextState(agentPos, action);
1488
+ const reward = getReward(newState);
1489
+
1490
+ // Update total reward for this episode
1491
+ currentEpisodeReward += reward;
1492
+
1493
+ // Update Q-value
1494
+ if (!isOptimalMode) {
1495
+ const maxFutureQ = Math.max(...qTable[newState]);
1496
+ qTable[agentPos][action] =
1497
+ qTable[agentPos][action] +
1498
+ learningRate *
1499
+ (reward + discountFactor * maxFutureQ - qTable[agentPos][action]);
1500
+ }
1501
+
1502
+ // Move agent
1503
+ moveAgent(newState);
1504
+ displayReward(reward, newState);
1505
+
1506
+ // Update display
1507
+ updateQValues();
1508
+
1509
+ // Update counters
1510
+ steps++;
1511
+ episodeSteps++;
1512
+ updateStats();
1513
+
1514
+ // Check if episode is done (only when reaching goal, not obstacles)
1515
+ if (isDone(newState) || episodeSteps > 100) {
1516
+ // If it reached the goal successfully
1517
+ if (newState === goal) {
1518
+ successCount++;
1519
+ totalCompletionSteps += episodeSteps;
1520
+
1521
+ // Check if it's a new record
1522
+ if (episodeSteps < bestPathSteps) {
1523
+ bestPathSteps = episodeSteps;
1524
+
1525
+ // Show notification
1526
+ showNotification(
1527
+ "New Record!",
1528
+ `Episode ${
1529
+ episodes + 1
1530
+ } found the goal in just ${episodeSteps} steps with a total reward of ${currentEpisodeReward.toFixed(
1531
+ 1
1532
+ )}!`
1533
+ );
1534
+ }
1535
+
1536
+ // Add to leaderboard
1537
+ leaderboard.push({
1538
+ episode: episodes + 1,
1539
+ steps: episodeSteps,
1540
+ reward: currentEpisodeReward,
1541
+ });
1542
+ updateLeaderboard();
1543
+ }
1544
+
1545
+ // Reset for next episode
1546
+ setTimeout(() => {
1547
+ episodes++;
1548
+
1549
+ // Store episode data for chart
1550
+ stepsHistory.push(episodeSteps);
1551
+ rewardsHistory.push(currentEpisodeReward);
1552
+ updateSimpleChart();
1553
+
1554
+ // Reduce exploration rate over time
1555
+ if (!isOptimalMode) {
1556
+ explorationRate = Math.max(0.01, explorationRate * 0.99);
1557
+ explorationRateValue.textContent = explorationRate.toFixed(2);
1558
+ explorationRateSlider.value = explorationRate;
1559
+ }
1560
+
1561
+ // Reset agent position
1562
+ agentPos = 0;
1563
+ moveAgent(agentPos);
1564
+ episodeSteps = 0;
1565
+ currentEpisodeReward = 0;
1566
+ updateStats();
1567
+
1568
+ // Continue learning if not single step
1569
+ if (isLearning && !isSingleStep) {
1570
+ setTimeout(step, animationSpeed);
1571
+ } else {
1572
+ isSingleStep = false;
1573
+ }
1574
+ }, animationSpeed);
1575
+ } else {
1576
+ // Continue episode
1577
+ if (isLearning && !isSingleStep) {
1578
+ setTimeout(step, animationSpeed);
1579
+ } else {
1580
+ isSingleStep = false;
1581
+ }
1582
+ }
1583
+ }
1584
+
1585
+ // Reset environment
1586
+ function resetEnvironment() {
1587
+ if (isLearning) {
1588
+ isLearning = false;
1589
+ startButton.textContent = "Start Learning";
1590
+ }
1591
+
1592
+ // Clear trails
1593
+ document.querySelectorAll(".trail").forEach((trail) => trail.remove());
1594
+ document
1595
+ .querySelectorAll(".reward-display")
1596
+ .forEach((display) => display.remove());
1597
+ document
1598
+ .querySelectorAll(".explore-indicator")
1599
+ .forEach((indicator) => indicator.remove());
1600
+
1601
+ // Reset agent position
1602
+ agentPos = 0;
1603
+ moveAgent(agentPos);
1604
+
1605
+ // Reset learning state
1606
+ qTable = Array(numStates)
1607
+ .fill()
1608
+ .map(() => Array(numActions).fill(0));
1609
+ episodes = 0;
1610
+ steps = 0;
1611
+ episodeSteps = 0;
1612
+ stepsHistory = [];
1613
+ rewardsHistory = [];
1614
+ successCount = 0;
1615
+ totalCompletionSteps = 0;
1616
+ bestPathSteps = Infinity;
1617
+ leaderboard = [];
1618
+ currentEpisodeReward = 0;
1619
+
1620
+ // Reset parameters to defaults
1621
+ learningRate = 0.1;
1622
+ discountFactor = 0.9;
1623
+ explorationRate = 0.3;
1624
+
1625
+ // Update sliders
1626
+ learningRateSlider.value = learningRate;
1627
+ learningRateValue.textContent = learningRate.toFixed(2);
1628
+ discountFactorSlider.value = discountFactor;
1629
+ discountFactorValue.textContent = discountFactor.toFixed(2);
1630
+ explorationRateSlider.value = explorationRate;
1631
+ explorationRateValue.textContent = explorationRate.toFixed(2);
1632
+
1633
+ // Update display
1634
+ updateQValues();
1635
+ updateStats();
1636
+ updateLeaderboard();
1637
+ updateSimpleChart();
1638
+ }
1639
+
1640
+ // Start/stop learning
1641
+ startButton.addEventListener("click", function () {
1642
+ if (isLearning) {
1643
+ isLearning = false;
1644
+ startButton.textContent = "Start Learning";
1645
+ } else {
1646
+ isLearning = true;
1647
+ startButton.textContent = "Pause Learning";
1648
+ step();
1649
+ }
1650
+ });
1651
+
1652
+ // Single step
1653
+ stepButton.addEventListener("click", function () {
1654
+ if (!isLearning) {
1655
+ isSingleStep = true;
1656
+ step();
1657
+ }
1658
+ });
1659
+
1660
+ // Reset environment
1661
+ resetButton.addEventListener("click", resetEnvironment);
1662
+
1663
+ // Initialize
1664
+ window.onload = function () {
1665
+ // Show loader while initializing
1666
+ setTimeout(() => {
1667
+ initializeGrid();
1668
+ updateQValues();
1669
+ createStars();
1670
+
1671
+ // Hide loader and show main content
1672
+ loader.style.display = "none";
1673
+ mainContent.style.display = "grid";
1674
+
1675
+ // Show welcome notification
1676
+ showNotification(
1677
+ "Welcome!",
1678
+ 'Click "Start Learning" to begin the Q-Learning simulation.',
1679
+ 5000
1680
+ );
1681
+ }, 800);
1682
+ };
1683
+ </script>
1684
+ </body>
1685
  </html>