arad1367 commited on
Commit
84bf0da
·
verified ·
1 Parent(s): e2fe14d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +716 -18
index.html CHANGED
@@ -1,19 +1,717 @@
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
+ <!-- Airplane Simulator 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>Simple RL Airplane Simulator</title>
8
+ <style>
9
+ body {
10
+ font-family: Arial, sans-serif;
11
+ margin: 0;
12
+ padding: 20px;
13
+ background-color: #f0f8ff;
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ }
18
+
19
+ canvas {
20
+ background: linear-gradient(to bottom, #87ceeb, #e0f7ff);
21
+ border: 1px solid #333;
22
+ margin-bottom: 20px;
23
+ }
24
+
25
+ .controls {
26
+ display: flex;
27
+ flex-wrap: wrap;
28
+ gap: 20px;
29
+ max-width: 800px;
30
+ margin-bottom: 20px;
31
+ }
32
+
33
+ .control-group {
34
+ background-color: white;
35
+ padding: 15px;
36
+ border-radius: 8px;
37
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
38
+ min-width: 250px;
39
+ }
40
+
41
+ h1,
42
+ h2 {
43
+ color: #1e3a8a;
44
+ }
45
+
46
+ button {
47
+ padding: 10px 15px;
48
+ margin: 5px;
49
+ background-color: #1e3a8a;
50
+ color: white;
51
+ border: none;
52
+ border-radius: 4px;
53
+ cursor: pointer;
54
+ }
55
+
56
+ button:hover {
57
+ background-color: #162a61;
58
+ }
59
+
60
+ button:disabled {
61
+ background-color: #ccc;
62
+ cursor: not-allowed;
63
+ }
64
+
65
+ .param-group {
66
+ margin-bottom: 10px;
67
+ }
68
+
69
+ label {
70
+ display: block;
71
+ margin-bottom: 5px;
72
+ font-weight: bold;
73
+ }
74
+
75
+ .footer {
76
+ margin-top: 30px;
77
+ text-align: center;
78
+ color: #555;
79
+ }
80
+ </style>
81
+ </head>
82
+ <body>
83
+ <h1>Simple RL Airplane Simulator</h1>
84
+
85
+ <canvas id="canvas" width="600" height="400"></canvas>
86
+
87
+ <div class="controls">
88
+ <div class="control-group">
89
+ <h2>RL Parameters</h2>
90
+
91
+ <div class="param-group">
92
+ <label for="learningRate"
93
+ >Learning Rate: <span id="learningRateValue">0.1</span></label
94
+ >
95
+ <input
96
+ type="range"
97
+ id="learningRate"
98
+ min="0.01"
99
+ max="0.5"
100
+ step="0.01"
101
+ value="0.1"
102
+ />
103
+ </div>
104
+
105
+ <div class="param-group">
106
+ <label for="epsilon"
107
+ >Exploration Rate: <span id="epsilonValue">0.3</span></label
108
+ >
109
+ <input
110
+ type="range"
111
+ id="epsilon"
112
+ min="0.01"
113
+ max="1"
114
+ step="0.01"
115
+ value="0.3"
116
+ />
117
+ </div>
118
+
119
+ <div class="param-group">
120
+ <label for="gamma"
121
+ >Discount Factor: <span id="gammaValue">0.9</span></label
122
+ >
123
+ <input
124
+ type="range"
125
+ id="gamma"
126
+ min="0.5"
127
+ max="0.99"
128
+ step="0.01"
129
+ value="0.9"
130
+ />
131
+ </div>
132
+
133
+ <button id="startBtn">Start Learning</button>
134
+ <button id="resetBtn">Reset Agent</button>
135
+ <button id="toggleHumanBtn">Toggle Human Control</button>
136
+ </div>
137
+
138
+ <div class="control-group">
139
+ <h2>Statistics</h2>
140
+ <p>Episode: <span id="episodeCounter">0</span></p>
141
+ <p>Current Reward: <span id="currentReward">0</span></p>
142
+ <p>Average Reward: <span id="avgReward">0</span></p>
143
+ <p>Altitude: <span id="altitude">100</span> m</p>
144
+ <p>Speed: <span id="speed">150</span> km/h</p>
145
+ <p>Pitch: <span id="pitch">0</span>°</p>
146
+ <p>Human Control: <span id="humanControlStatus">Off</span></p>
147
+ </div>
148
+ </div>
149
+
150
+ <div class="control-group" style="width: 100%; max-width: 800px">
151
+ <h2>Learning Progress</h2>
152
+ <div
153
+ id="infoPanel"
154
+ style="
155
+ height: 100px;
156
+ overflow-y: auto;
157
+ border: 1px solid #ccc;
158
+ padding: 10px;
159
+ background-color: #f9f9f9;
160
+ "
161
+ ></div>
162
+ </div>
163
+
164
+ <div class="footer">
165
+ <p>Flight Simulator Designed by Pejman</p>
166
+ <p>&copy; 2025 Pejman Ebrahimi - All Rights Reserved</p>
167
+ </div>
168
+
169
+ <script>
170
+ // Get canvas and context
171
+ const canvas = document.getElementById("canvas");
172
+ const ctx = canvas.getContext("2d");
173
+
174
+ // Game state
175
+ let isLearning = false;
176
+ let isHumanControl = false;
177
+ let frameCount = 0;
178
+ let episode = 0;
179
+ let currentReward = 0;
180
+ let rewardHistory = [];
181
+
182
+ // Airplane state
183
+ const airplane = {
184
+ x: 300,
185
+ y: 200,
186
+ altitude: 100,
187
+ speed: 150,
188
+ pitch: 0,
189
+ crashed: false,
190
+ };
191
+
192
+ // RL parameters
193
+ let learningRate = 0.1;
194
+ let epsilon = 0.3;
195
+ let gamma = 0.9;
196
+
197
+ // Q-learning variables
198
+ const qTable = {};
199
+ const actions = [
200
+ "nothing",
201
+ "pitch_up",
202
+ "pitch_down",
203
+ "throttle_up",
204
+ "throttle_down",
205
+ ];
206
+
207
+ // Key states for human control
208
+ const keys = {
209
+ up: false,
210
+ down: false,
211
+ left: false,
212
+ right: false,
213
+ };
214
+
215
+ // UI elements
216
+ const startBtn = document.getElementById("startBtn");
217
+ const resetBtn = document.getElementById("resetBtn");
218
+ const toggleHumanBtn = document.getElementById("toggleHumanBtn");
219
+ const infoPanel = document.getElementById("infoPanel");
220
+
221
+ // Initialize event listeners
222
+ function init() {
223
+ // Parameter sliders
224
+ document
225
+ .getElementById("learningRate")
226
+ .addEventListener("input", function () {
227
+ learningRate = parseFloat(this.value);
228
+ document.getElementById("learningRateValue").textContent =
229
+ learningRate.toFixed(2);
230
+ });
231
+
232
+ document
233
+ .getElementById("epsilon")
234
+ .addEventListener("input", function () {
235
+ epsilon = parseFloat(this.value);
236
+ document.getElementById("epsilonValue").textContent =
237
+ epsilon.toFixed(2);
238
+ });
239
+
240
+ document.getElementById("gamma").addEventListener("input", function () {
241
+ gamma = parseFloat(this.value);
242
+ document.getElementById("gammaValue").textContent = gamma.toFixed(2);
243
+ });
244
+
245
+ // Buttons
246
+ startBtn.addEventListener("click", function () {
247
+ console.log("Start button clicked");
248
+ if (!isLearning) {
249
+ isLearning = true;
250
+ isHumanControl = false;
251
+ episode = 0;
252
+ resetAirplane();
253
+ startBtn.textContent = "Learning...";
254
+ startBtn.disabled = true;
255
+ document.getElementById("humanControlStatus").textContent = "Off";
256
+ logInfo("Started learning process");
257
+ }
258
+ });
259
+
260
+ resetBtn.addEventListener("click", function () {
261
+ isLearning = false;
262
+ resetAirplane();
263
+ qTable = {};
264
+ episode = 0;
265
+ rewardHistory = [];
266
+ startBtn.textContent = "Start Learning";
267
+ startBtn.disabled = false;
268
+ logInfo("Reset agent and Q-table");
269
+ updateStats();
270
+ });
271
+
272
+ toggleHumanBtn.addEventListener("click", function () {
273
+ isHumanControl = !isHumanControl;
274
+ isLearning = false;
275
+ document.getElementById("humanControlStatus").textContent =
276
+ isHumanControl ? "On" : "Off";
277
+ startBtn.disabled = isHumanControl;
278
+ resetAirplane();
279
+ logInfo(
280
+ isHumanControl
281
+ ? "Switched to manual control"
282
+ : "Switched to AI mode"
283
+ );
284
+ });
285
+
286
+ // Keyboard controls
287
+ window.addEventListener("keydown", function (e) {
288
+ if (isHumanControl) {
289
+ switch (e.key) {
290
+ case "ArrowUp":
291
+ keys.up = true;
292
+ break;
293
+ case "ArrowDown":
294
+ keys.down = true;
295
+ break;
296
+ case "ArrowLeft":
297
+ keys.left = true;
298
+ break;
299
+ case "ArrowRight":
300
+ keys.right = true;
301
+ break;
302
+ }
303
+ }
304
+ });
305
+
306
+ window.addEventListener("keyup", function (e) {
307
+ switch (e.key) {
308
+ case "ArrowUp":
309
+ keys.up = false;
310
+ break;
311
+ case "ArrowDown":
312
+ keys.down = false;
313
+ break;
314
+ case "ArrowLeft":
315
+ keys.left = false;
316
+ break;
317
+ case "ArrowRight":
318
+ keys.right = false;
319
+ break;
320
+ }
321
+ });
322
+
323
+ // Start game loop
324
+ gameLoop();
325
+ }
326
+
327
+ // Helper: Log info to panel
328
+ function logInfo(message) {
329
+ const entry = document.createElement("div");
330
+ entry.textContent = `Frame ${frameCount}: ${message}`;
331
+ infoPanel.appendChild(entry);
332
+ infoPanel.scrollTop = infoPanel.scrollHeight;
333
+ console.log(message); // Also log to console for debugging
334
+ }
335
+
336
+ // Helper: Reset airplane
337
+ function resetAirplane() {
338
+ airplane.x = 300;
339
+ airplane.y = 200;
340
+ airplane.altitude = 100;
341
+ airplane.speed = 150;
342
+ airplane.pitch = 0;
343
+ airplane.crashed = false;
344
+ frameCount = 0;
345
+ currentReward = 0;
346
+ updateStats();
347
+ }
348
+
349
+ // Helper: Update stats display
350
+ function updateStats() {
351
+ document.getElementById("episodeCounter").textContent = episode;
352
+ document.getElementById("currentReward").textContent =
353
+ currentReward.toFixed(1);
354
+ document.getElementById("altitude").textContent = Math.round(
355
+ airplane.altitude
356
+ );
357
+ document.getElementById("speed").textContent = Math.round(
358
+ airplane.speed
359
+ );
360
+ document.getElementById("pitch").textContent = Math.round(
361
+ airplane.pitch
362
+ );
363
+
364
+ // Calculate average reward
365
+ if (rewardHistory.length > 0) {
366
+ const sum = rewardHistory.reduce((a, b) => a + b, 0);
367
+ const avg = sum / rewardHistory.length;
368
+ document.getElementById("avgReward").textContent = avg.toFixed(1);
369
+ } else {
370
+ document.getElementById("avgReward").textContent = "0";
371
+ }
372
+ }
373
+
374
+ // RL: Get state key for Q-table
375
+ function getStateKey() {
376
+ // Discretize the continuous state
377
+ const altBucket = Math.floor(airplane.altitude / 50);
378
+ const speedBucket = Math.floor(airplane.speed / 20);
379
+ const pitchBucket = Math.floor(airplane.pitch / 5);
380
+
381
+ return `${altBucket}_${speedBucket}_${pitchBucket}`;
382
+ }
383
+
384
+ // RL: Choose action using epsilon-greedy policy
385
+ function chooseAction(stateKey) {
386
+ // Ensure state exists in Q-table
387
+ if (!qTable[stateKey]) {
388
+ qTable[stateKey] = {};
389
+ actions.forEach((action) => {
390
+ qTable[stateKey][action] = 0;
391
+ });
392
+ }
393
+
394
+ // Exploration: choose random action
395
+ if (Math.random() < epsilon) {
396
+ const randomAction =
397
+ actions[Math.floor(Math.random() * actions.length)];
398
+ logInfo(`Exploring: Random action ${randomAction}`);
399
+ return randomAction;
400
+ }
401
+
402
+ // Exploitation: choose best action
403
+ let bestAction = actions[0];
404
+ let bestValue = qTable[stateKey][bestAction];
405
+
406
+ actions.forEach((action) => {
407
+ if (qTable[stateKey][action] > bestValue) {
408
+ bestValue = qTable[stateKey][action];
409
+ bestAction = action;
410
+ }
411
+ });
412
+
413
+ logInfo(`Exploiting: Best action ${bestAction}`);
414
+ return bestAction;
415
+ }
416
+
417
+ // RL: Update Q-value
418
+ function updateQValue(stateKey, action, reward, nextStateKey) {
419
+ // Ensure states exist in Q-table
420
+ if (!qTable[stateKey]) {
421
+ qTable[stateKey] = {};
422
+ actions.forEach((a) => (qTable[stateKey][a] = 0));
423
+ }
424
+
425
+ if (!qTable[nextStateKey]) {
426
+ qTable[nextStateKey] = {};
427
+ actions.forEach((a) => (qTable[nextStateKey][a] = 0));
428
+ }
429
+
430
+ // Find max Q-value for next state
431
+ let maxNextQ = Math.max(...actions.map((a) => qTable[nextStateKey][a]));
432
+
433
+ // Update rule: Q(s,a) = Q(s,a) + α * (r + γ * max(Q(s')) - Q(s,a))
434
+ const oldValue = qTable[stateKey][action];
435
+ const newValue =
436
+ oldValue + learningRate * (reward + gamma * maxNextQ - oldValue);
437
+ qTable[stateKey][action] = newValue;
438
+
439
+ logInfo(
440
+ `Updated Q(${stateKey}, ${action}): ${oldValue.toFixed(
441
+ 2
442
+ )} → ${newValue.toFixed(2)}`
443
+ );
444
+ }
445
+
446
+ // Apply the chosen action
447
+ function applyAction(action) {
448
+ switch (action) {
449
+ case "pitch_up":
450
+ airplane.pitch += 2;
451
+ break;
452
+ case "pitch_down":
453
+ airplane.pitch -= 2;
454
+ break;
455
+ case "throttle_up":
456
+ airplane.speed += 10;
457
+ break;
458
+ case "throttle_down":
459
+ airplane.speed -= 10;
460
+ break;
461
+ // 'nothing' case does nothing
462
+ }
463
+
464
+ // Clamp values to reasonable ranges
465
+ airplane.pitch = Math.max(-30, Math.min(30, airplane.pitch));
466
+ airplane.speed = Math.max(50, Math.min(300, airplane.speed));
467
+ }
468
+
469
+ // Apply human controls
470
+ function applyHumanControl() {
471
+ if (keys.up) {
472
+ airplane.pitch += 1;
473
+ }
474
+ if (keys.down) {
475
+ airplane.pitch -= 1;
476
+ }
477
+ if (keys.left) {
478
+ airplane.speed -= 2;
479
+ }
480
+ if (keys.right) {
481
+ airplane.speed += 2;
482
+ }
483
+
484
+ // Clamp values
485
+ airplane.pitch = Math.max(-30, Math.min(30, airplane.pitch));
486
+ airplane.speed = Math.max(50, Math.min(300, airplane.speed));
487
+ }
488
+
489
+ // Update physics
490
+ function updatePhysics() {
491
+ // Update altitude based on pitch and speed
492
+ const pitchFactor = Math.sin((airplane.pitch * Math.PI) / 180);
493
+ airplane.altitude += pitchFactor * airplane.speed * 0.05;
494
+
495
+ // Apply gravity
496
+ airplane.altitude -= 0.5;
497
+
498
+ // Add some turbulence
499
+ airplane.pitch += Math.random() - 0.5;
500
+
501
+ // Gradual return to level flight (stabilization)
502
+ airplane.pitch *= 0.98;
503
+
504
+ // Check for crash
505
+ if (airplane.altitude <= 0) {
506
+ airplane.altitude = 0;
507
+ airplane.crashed = true;
508
+ logInfo("CRASHED!");
509
+ }
510
+
511
+ updateStats();
512
+ }
513
+
514
+ // Calculate reward
515
+ function calculateReward() {
516
+ let reward = 0;
517
+
518
+ // Base reward for staying airborne
519
+ reward += 1;
520
+
521
+ // Reward for being at the target altitude (100-200m)
522
+ if (airplane.altitude > 100 && airplane.altitude < 200) {
523
+ reward += 2;
524
+ } else if (airplane.altitude < 50 || airplane.altitude > 300) {
525
+ reward -= 2;
526
+ }
527
+
528
+ // Reward for good speed
529
+ if (airplane.speed > 100 && airplane.speed < 200) {
530
+ reward += 1;
531
+ } else if (airplane.speed < 80 || airplane.speed > 220) {
532
+ reward -= 1;
533
+ }
534
+
535
+ // Penalty for extreme pitch
536
+ if (Math.abs(airplane.pitch) > 20) {
537
+ reward -= 2;
538
+ }
539
+
540
+ // Big penalty for crashing
541
+ if (airplane.crashed) {
542
+ reward -= 50;
543
+ }
544
+
545
+ return reward;
546
+ }
547
+
548
+ // Draw the scene
549
+ function draw() {
550
+ // Clear canvas
551
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
552
+
553
+ // Draw sky
554
+ const skyGradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
555
+ skyGradient.addColorStop(0, "#1e90ff");
556
+ skyGradient.addColorStop(1, "#87ceeb");
557
+ ctx.fillStyle = skyGradient;
558
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
559
+
560
+ // Draw ground
561
+ const groundY = canvas.height - airplane.altitude * 0.8;
562
+ if (groundY < canvas.height) {
563
+ ctx.fillStyle = "#8B4513";
564
+ ctx.fillRect(0, groundY, canvas.width, canvas.height);
565
+ }
566
+
567
+ // Draw airplane
568
+ ctx.save();
569
+ ctx.translate(airplane.x, airplane.y);
570
+ ctx.rotate((airplane.pitch * Math.PI) / 180);
571
+
572
+ // Draw airplane body
573
+ ctx.fillStyle = airplane.crashed ? "red" : "white";
574
+ ctx.beginPath();
575
+ ctx.moveTo(20, 0); // nose
576
+ ctx.lineTo(-20, 10); // bottom rear
577
+ ctx.lineTo(-10, 0); // tail
578
+ ctx.lineTo(-20, -10); // top rear
579
+ ctx.closePath();
580
+ ctx.fill();
581
+ ctx.strokeStyle = "#000";
582
+ ctx.lineWidth = 2;
583
+ ctx.stroke();
584
+
585
+ // Draw wings
586
+ ctx.fillStyle = "#ccc";
587
+ ctx.beginPath();
588
+ ctx.moveTo(0, 0);
589
+ ctx.lineTo(-5, 30);
590
+ ctx.lineTo(5, 30);
591
+ ctx.closePath();
592
+ ctx.fill();
593
+ ctx.stroke();
594
+
595
+ ctx.beginPath();
596
+ ctx.moveTo(0, 0);
597
+ ctx.lineTo(-5, -30);
598
+ ctx.lineTo(5, -30);
599
+ ctx.closePath();
600
+ ctx.fill();
601
+ ctx.stroke();
602
+
603
+ ctx.restore();
604
+
605
+ // Draw HUD
606
+ ctx.font = "14px Arial";
607
+ ctx.fillStyle = "white";
608
+ ctx.fillText(`Altitude: ${Math.round(airplane.altitude)}m`, 20, 30);
609
+ ctx.fillText(`Speed: ${Math.round(airplane.speed)} km/h`, 20, 50);
610
+ ctx.fillText(`Pitch: ${Math.round(airplane.pitch)}°`, 20, 70);
611
+ ctx.fillText(
612
+ `Mode: ${isHumanControl ? "Human" : isLearning ? "Learning" : "AI"}`,
613
+ 20,
614
+ 90
615
+ );
616
+
617
+ // Draw altitude indicator
618
+ const altIndicatorX = canvas.width - 50;
619
+ const altIndicatorTop = 50;
620
+ const altIndicatorHeight = 300;
621
+
622
+ ctx.fillStyle = "rgba(0,0,0,0.5)";
623
+ ctx.fillRect(
624
+ altIndicatorX - 15,
625
+ altIndicatorTop,
626
+ 30,
627
+ altIndicatorHeight
628
+ );
629
+
630
+ const markerPosition =
631
+ altIndicatorTop +
632
+ altIndicatorHeight -
633
+ (airplane.altitude / 200) * altIndicatorHeight;
634
+ ctx.fillStyle = "lime";
635
+ ctx.beginPath();
636
+ ctx.moveTo(altIndicatorX - 20, markerPosition);
637
+ ctx.lineTo(altIndicatorX, markerPosition - 10);
638
+ ctx.lineTo(altIndicatorX, markerPosition + 10);
639
+ ctx.closePath();
640
+ ctx.fill();
641
+ }
642
+
643
+ // Main game loop
644
+ function gameLoop() {
645
+ frameCount++;
646
+
647
+ if (isHumanControl) {
648
+ // Human control mode
649
+ applyHumanControl();
650
+ updatePhysics();
651
+ } else if (isLearning) {
652
+ // AI learning mode
653
+ const currentState = getStateKey();
654
+
655
+ // Choose and apply action
656
+ const action = chooseAction(currentState);
657
+ applyAction(action);
658
+
659
+ // Update physics
660
+ updatePhysics();
661
+
662
+ // Calculate reward
663
+ const reward = calculateReward();
664
+ currentReward += reward;
665
+
666
+ // Get next state
667
+ const nextState = getStateKey();
668
+
669
+ // Update Q-table
670
+ updateQValue(currentState, action, reward, nextState);
671
+
672
+ // Check if episode is complete
673
+ if (airplane.crashed || frameCount > 1000) {
674
+ // Log episode result
675
+ logInfo(
676
+ `Episode ${episode} finished with reward: ${currentReward.toFixed(
677
+ 1
678
+ )}`
679
+ );
680
+ rewardHistory.push(currentReward);
681
+
682
+ // Move to next episode
683
+ episode++;
684
+ document.getElementById("episodeCounter").textContent = episode;
685
+
686
+ // Reset for next episode
687
+ resetAirplane();
688
+
689
+ // Reduce exploration rate over time (optional)
690
+ if (epsilon > 0.05) {
691
+ epsilon *= 0.99;
692
+ document.getElementById("epsilonValue").textContent =
693
+ epsilon.toFixed(2);
694
+ document.getElementById("epsilon").value = epsilon;
695
+ }
696
+ }
697
+ } else {
698
+ // Idle mode - just apply physics with small drone movement
699
+ if (frameCount % 60 === 0) {
700
+ // Make small random adjustments
701
+ airplane.pitch += (Math.random() - 0.5) * 2;
702
+ }
703
+ updatePhysics();
704
+ }
705
+
706
+ // Draw everything
707
+ draw();
708
+
709
+ // Continue game loop
710
+ requestAnimationFrame(gameLoop);
711
+ }
712
+
713
+ // Start everything
714
+ window.onload = init;
715
+ </script>
716
+ </body>
717
  </html>