openfree commited on
Commit
85d6e29
ยท
verified ยท
1 Parent(s): a30aae8

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1440 -19
index.html CHANGED
@@ -1,19 +1,1440 @@
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
+ // Feature to detect when we've completed the course
2
+ function checkCourseCompletion() {
3
+ const bestCar = getBestCar();
4
+
5
+ if (bestCar && bestCar.checkpointIndex === track.checkpoints.length) {
6
+ // Launch a bunch of celebratory confetti!
7
+ createConfetti(100, canvas.width/2, canvas.height/2);
8
+ setTimeout(() => createConfetti(50, canvas.width/4, canvas.height/2), 300);
9
+ setTimeout(() => createConfetti(50, 3*canvas.width/4, canvas.height/2), 600);
10
+
11
+ // Show victory message
12
+ const message = document.createElement('div');
13
+ message.style.position = 'absolute';
14
+ message.style.top = '50%';
15
+ message.style.left = '50%';
16
+ message.style.transform = 'translate(-50%, -50%)';
17
+ message.style.background = 'rgba(16, 185, 129, 0.9)';
18
+ message.style.color = 'white';
19
+ message.style.padding = '20px';
20
+ message.style.borderRadius = '10px';
21
+ message.style.fontSize = '24px';
22
+ message.style.fontWeight = 'bold';
23
+ message.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
24
+ message.style.zIndex = '1000';
25
+ message.style.textAlign = 'center';
26
+ message.innerHTML = `
27
+ <div>๐Ÿ† Course Completed! ๐Ÿ†</div>
28
+ <div style="font-size: 16px; margin-top: 10px;">
29
+ Generations: ${generation}<br>
30
+ Fitness: ${Math.round(bestCar.fitness * 1000)}
31
+ </div>
32
+ <button id="continueBtn" style="
33
+ background-color: white;
34
+ color: #10b981;
35
+ border: none;
36
+ padding: 8px 16px;
37
+ border-radius: 5px;
38
+ margin-top: 15px;
39
+ cursor: pointer;
40
+ font-weight: bold;">
41
+ Continue Training
42
+ </button>
43
+ `;
44
+
45
+ document.body.appendChild(message);
46
+
47
+ // Pause simulation
48
+ isRunning = false;
49
+ cancelAnimationFrame(animationId);
50
+
51
+ // Event listener for the continue button
52
+ document.getElementById('continueBtn').addEventListener('click', () => {
53
+ document.body.removeChild(message);
54
+ isRunning = true;
55
+ lastUpdateTime = performance.now();
56
+ animate();
57
+ });
58
+ }
59
+ }<!DOCTYPE html>
60
+ <html lang="en">
61
+ <head>
62
+ <meta charset="UTF-8">
63
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
64
+ <title>AI Driving Simulation</title>
65
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
66
+ <style>
67
+ body {
68
+ background-color: #111827;
69
+ color: #f3f4f6;
70
+ font-family: Arial, sans-serif;
71
+ line-height: 1.5;
72
+ margin: 0;
73
+ padding: 20px;
74
+ }
75
+
76
+ .container {
77
+ max-width: 1200px;
78
+ margin: 0 auto;
79
+ }
80
+
81
+ h1 {
82
+ color: #60a5fa;
83
+ text-align: center;
84
+ margin-bottom: 20px;
85
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
86
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
87
+ letter-spacing: 1px;
88
+ }
89
+
90
+ canvas {
91
+ background-color: #2d3748;
92
+ border-radius: 12px;
93
+ display: block;
94
+ margin: 0 auto 20px;
95
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
96
+ border: 2px solid #4a5568;
97
+ }
98
+
99
+ .controls {
100
+ display: flex;
101
+ gap: 10px;
102
+ margin-bottom: 20px;
103
+ justify-content: center;
104
+ flex-wrap: wrap;
105
+ }
106
+
107
+ button {
108
+ background-color: #3b82f6;
109
+ color: white;
110
+ border: none;
111
+ padding: 10px 18px;
112
+ border-radius: 8px;
113
+ cursor: pointer;
114
+ font-weight: bold;
115
+ transition: all 0.2s ease;
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 5px;
119
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
120
+ }
121
+
122
+ button:hover {
123
+ background-color: #2563eb;
124
+ transform: translateY(-2px);
125
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
126
+ }
127
+
128
+ button:active {
129
+ transform: translateY(1px);
130
+ }
131
+
132
+ .start-btn {
133
+ background-color: #10b981;
134
+ }
135
+
136
+ .start-btn:hover {
137
+ background-color: #059669;
138
+ }
139
+
140
+ .pause-btn {
141
+ background-color: #f59e0b;
142
+ }
143
+
144
+ .pause-btn:hover {
145
+ background-color: #d97706;
146
+ }
147
+
148
+ .reset-btn {
149
+ background-color: #ef4444;
150
+ }
151
+
152
+ .reset-btn:hover {
153
+ background-color: #dc2626;
154
+ }
155
+
156
+ .stats {
157
+ display: flex;
158
+ justify-content: space-between;
159
+ background-color: #1f2937;
160
+ padding: 18px;
161
+ border-radius: 12px;
162
+ margin-bottom: 20px;
163
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
164
+ flex-wrap: wrap;
165
+ gap: 10px;
166
+ border: 1px solid #374151;
167
+ }
168
+
169
+ .stat-item {
170
+ text-align: center;
171
+ background-color: #2d3748;
172
+ padding: 10px 15px;
173
+ border-radius: 8px;
174
+ min-width: 90px;
175
+ transition: all 0.3s ease;
176
+ }
177
+
178
+ .stat-item:hover {
179
+ transform: translateY(-2px);
180
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
181
+ }
182
+
183
+ .stat-value {
184
+ font-size: 1.4em;
185
+ font-weight: bold;
186
+ color: #60a5fa;
187
+ margin-top: 5px;
188
+ }
189
+
190
+ .settings {
191
+ background-color: #1f2937;
192
+ padding: 20px;
193
+ border-radius: 12px;
194
+ margin-bottom: 20px;
195
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
196
+ border: 1px solid #374151;
197
+ }
198
+
199
+ .slider-container {
200
+ margin-bottom: 15px;
201
+ background-color: #2d3748;
202
+ padding: 12px;
203
+ border-radius: 8px;
204
+ }
205
+
206
+ .slider-container label {
207
+ display: block;
208
+ margin-bottom: 8px;
209
+ font-weight: 500;
210
+ color: #d1d5db;
211
+ }
212
+
213
+ input[type="range"] {
214
+ width: 100%;
215
+ margin-bottom: 8px;
216
+ height: 6px;
217
+ -webkit-appearance: none;
218
+ background: #4b5563;
219
+ border-radius: 5px;
220
+ outline: none;
221
+ }
222
+
223
+ input[type="range"]::-webkit-slider-thumb {
224
+ -webkit-appearance: none;
225
+ width: 18px;
226
+ height: 18px;
227
+ background: #3b82f6;
228
+ border-radius: 50%;
229
+ cursor: pointer;
230
+ transition: background 0.2s;
231
+ }
232
+
233
+ input[type="range"]::-webkit-slider-thumb:hover {
234
+ background: #2563eb;
235
+ }
236
+
237
+ .progress-container {
238
+ background-color: #374151;
239
+ height: 10px;
240
+ border-radius: 5px;
241
+ margin-top: 10px;
242
+ overflow: hidden;
243
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
244
+ }
245
+
246
+ .progress-bar {
247
+ height: 100%;
248
+ background: linear-gradient(90deg, #10b981, #3b82f6);
249
+ width: 0;
250
+ transition: width 0.3s;
251
+ box-shadow: 0 0 5px rgba(16, 185, 129, 0.5);
252
+ }
253
+
254
+ /* Car emoji styling */
255
+ .car-emoji {
256
+ font-size: 1.2em;
257
+ margin-right: 5px;
258
+ }
259
+
260
+ /* Added visual elements */
261
+ .section-title {
262
+ display: flex;
263
+ align-items: center;
264
+ margin-bottom: 15px;
265
+ border-bottom: 1px solid #374151;
266
+ padding-bottom: 8px;
267
+ }
268
+
269
+ .section-title i {
270
+ margin-right: 8px;
271
+ color: #60a5fa;
272
+ }
273
+
274
+ /* Custom checkbox */
275
+ .toggle-switch {
276
+ position: relative;
277
+ display: inline-block;
278
+ width: 50px;
279
+ height: 24px;
280
+ }
281
+
282
+ .toggle-switch input {
283
+ opacity: 0;
284
+ width: 0;
285
+ height: 0;
286
+ }
287
+
288
+ .toggle-slider {
289
+ position: absolute;
290
+ cursor: pointer;
291
+ top: 0;
292
+ left: 0;
293
+ right: 0;
294
+ bottom: 0;
295
+ background-color: #4b5563;
296
+ transition: .4s;
297
+ border-radius: 24px;
298
+ }
299
+
300
+ .toggle-slider:before {
301
+ position: absolute;
302
+ content: "";
303
+ height: 16px;
304
+ width: 16px;
305
+ left: 4px;
306
+ bottom: 4px;
307
+ background-color: white;
308
+ transition: .4s;
309
+ border-radius: 50%;
310
+ }
311
+
312
+ input:checked + .toggle-slider {
313
+ background-color: #3b82f6;
314
+ }
315
+
316
+ input:checked + .toggle-slider:before {
317
+ transform: translateX(26px);
318
+ }
319
+ </style>
320
+ </head>
321
+ <body>
322
+ <div class="container">
323
+ <h1>๐ŸŽ๏ธ AI Driving Simulation</h1>
324
+
325
+ <canvas id="simulationCanvas" width="800" height="500"></canvas>
326
+
327
+ <div class="controls">
328
+ <button id="startBtn" class="start-btn"><i class="fas fa-play"></i> Start</button>
329
+ <button id="pauseBtn" class="pause-btn"><i class="fas fa-pause"></i> Pause</button>
330
+ <button id="resetBtn" class="reset-btn"><i class="fas fa-sync-alt"></i> New Track</button>
331
+ <button id="saveBtn"><i class="fas fa-save"></i> Save Model</button>
332
+ <button id="loadBtn"><i class="fas fa-upload"></i> Load Model</button>
333
+ </div>
334
+
335
+ <div class="stats">
336
+ <div class="stat-item">
337
+ <div><i class="fas fa-dna"></i> Generation</div>
338
+ <div id="generationCount" class="stat-value">0</div>
339
+ </div>
340
+ <div class="stat-item">
341
+ <div><i class="fas fa-car"></i> Alive</div>
342
+ <div class="stat-value"><span id="aliveCount">0</span>/<span id="populationCount">0</span></div>
343
+ </div>
344
+ <div class="stat-item">
345
+ <div><i class="fas fa-trophy"></i> Best Fitness</div>
346
+ <div id="maxFitness" class="stat-value">0</div>
347
+ </div>
348
+ <div class="stat-item">
349
+ <div><i class="fas fa-tachometer-alt"></i> FPS</div>
350
+ <div id="fpsCounter" class="stat-value">0</div>
351
+ </div>
352
+ </div>
353
+
354
+ <div class="settings">
355
+ <div class="section-title">
356
+ <i class="fas fa-sliders-h"></i> <h3>Simulation Settings</h3>
357
+ </div>
358
+
359
+ <div class="slider-container">
360
+ <label for="populationSlider"><i class="fas fa-users"></i> Population Size:</label>
361
+ <input type="range" id="populationSlider" min="10" max="300" value="100">
362
+ <span id="populationValue">100</span>
363
+ </div>
364
+
365
+ <div class="slider-container">
366
+ <label for="mutationSlider"><i class="fas fa-random"></i> Mutation Rate:</label>
367
+ <input type="range" id="mutationSlider" min="1" max="100" value="10">
368
+ <span id="mutationValue">10%</span>
369
+ </div>
370
+
371
+ <div class="slider-container">
372
+ <label for="speedSlider"><i class="fas fa-fast-forward"></i> Simulation Speed:</label>
373
+ <input type="range" id="speedSlider" min="1" max="30" value="5">
374
+ <span id="speedValue">15x</span>
375
+ </div>
376
+
377
+ <div class="slider-container">
378
+ <label><i class="fas fa-flag-checkered"></i> Track Progress:</label>
379
+ <div class="progress-container">
380
+ <div id="bestProgressBar" class="progress-bar"></div>
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ <div class="settings">
386
+ <div class="section-title">
387
+ <i class="fas fa-info-circle"></i> <h3>About This Simulation</h3>
388
+ </div>
389
+ <p>This simulation demonstrates how AI can learn to drive using genetic algorithms and neural networks. Cars must navigate randomly generated tracks without any prior knowledge of the environment.</p>
390
+ <p><strong>Key Improvements:</strong></p>
391
+ <ul>
392
+ <li><i class="fas fa-brain"></i> <strong>Enhanced Neural Network:</strong> Using Sigmoid activation function for smoother decision making</li>
393
+ <li><i class="fas fa-random"></i> <strong>Crossover:</strong> Combining the best traits from parent models</li>
394
+ <li><i class="fas fa-chart-line"></i> <strong>Adaptive Mutation:</strong> Automatically adjusts as generations progress</li>
395
+ <li><i class="fas fa-bolt"></i> <strong>Performance Optimization:</strong> Delta-time based updates for consistent simulation</li>
396
+ <li><i class="fas fa-exclamation-triangle"></i> <strong>Improved Collision Detection:</strong> More accurate polygon-based detection</li>
397
+ <li><i class="fas fa-save"></i> <strong>Model Saving:</strong> Save and load your best models</li>
398
+ </ul>
399
+ </div>
400
+ </div>
401
+
402
+ <script>
403
+ document.addEventListener('DOMContentLoaded', () => {
404
+ // Canvas ์„ค์ •
405
+ const canvas = document.getElementById('simulationCanvas');
406
+ const ctx = canvas.getContext('2d');
407
+
408
+ // UI ์š”์†Œ
409
+ const startBtn = document.getElementById('startBtn');
410
+ const pauseBtn = document.getElementById('pauseBtn');
411
+ const resetBtn = document.getElementById('resetBtn');
412
+ const saveBtn = document.getElementById('saveBtn');
413
+ const loadBtn = document.getElementById('loadBtn');
414
+
415
+ const populationSlider = document.getElementById('populationSlider');
416
+ const mutationSlider = document.getElementById('mutationSlider');
417
+ const speedSlider = document.getElementById('speedSlider');
418
+
419
+ const populationValue = document.getElementById('populationValue');
420
+ const mutationValue = document.getElementById('mutationValue');
421
+ const speedValue = document.getElementById('speedValue');
422
+
423
+ const generationCount = document.getElementById('generationCount');
424
+ const aliveCount = document.getElementById('aliveCount');
425
+ const populationCount = document.getElementById('populationCount');
426
+ const maxFitness = document.getElementById('maxFitness');
427
+ const fpsCounter = document.getElementById('fpsCounter');
428
+ const bestProgressBar = document.getElementById('bestProgressBar');
429
+
430
+ // ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋งค๊ฐœ๋ณ€์ˆ˜
431
+ let populationSize = parseInt(populationSlider.value);
432
+ let mutationRate = parseInt(mutationSlider.value) / 100;
433
+ let simulationSpeed = parseInt(speedSlider.value) * 3; // 3๋ฐฐ ๋น ๋ฅธ ์†๋„
434
+ let isRunning = false;
435
+ let generation = 0;
436
+ let fps = 0;
437
+ let bestCarProgress = 0;
438
+ let deltaTime = 0;
439
+ let lastUpdateTime = 0;
440
+ let frameCount = 0;
441
+ let lastFpsUpdate = 0;
442
+
443
+ // ํ™œ์„ฑํ™” ํ•จ์ˆ˜
444
+ const sigmoid = (x) => 1 / (1 + Math.exp(-x));
445
+ const relu = (x) => Math.max(0, x);
446
+
447
+ // ํŠธ๋ž™ ์ •์˜
448
+ const track = {
449
+ walls: [],
450
+ checkpoints: [],
451
+ startPosition: { x: 100, y: 250, angle: 0 },
452
+
453
+ generateRandomTrack() {
454
+ this.walls = [];
455
+ this.checkpoints = [];
456
+
457
+ // ์™ธ๋ถ€ ๊ฒฝ๊ณ„ ๋ฒฝ (ํ•ญ์ƒ ์กด์žฌ)
458
+ this.walls.push(
459
+ { x: 50, y: 50, width: 700, height: 20 }, // ์ƒ๋‹จ
460
+ { x: 50, y: 50, width: 20, height: 400 }, // ์ขŒ์ธก
461
+ { x: 50, y: 430, width: 700, height: 20 }, // ํ•˜๋‹จ
462
+ { x: 730, y: 50, width: 20, height: 400 } // ์šฐ์ธก
463
+ );
464
+
465
+ // ๋ฌด์ž‘์œ„ ์žฅ์• ๋ฌผ ์ƒ์„ฑ
466
+ const obstacleCount = 3 + Math.floor(Math.random() * 6);
467
+ for (let i = 0; i < obstacleCount; i++) {
468
+ const isVertical = Math.random() > 0.5;
469
+ let x, y, width, height;
470
+
471
+ if (isVertical) {
472
+ width = 20;
473
+ height = 50 + Math.random() * 200;
474
+ x = 100 + Math.random() * 600;
475
+ y = 100 + Math.random() * (400 - height);
476
+ } else {
477
+ width = 50 + Math.random() * 200;
478
+ height = 20;
479
+ x = 100 + Math.random() * (700 - width);
480
+ y = 100 + Math.random() * 300;
481
+ }
482
+
483
+ // ์‹œ์ž‘ ์œ„์น˜๋ฅผ ๋ง‰์ง€ ์•Š๋„๋ก ํ™•์ธ
484
+ if (!(x < 150 && y < 300 && y + height > 200)) {
485
+ this.walls.push({ x, y, width, height });
486
+ }
487
+ }
488
+
489
+ // ์ฒดํฌํฌ์ธํŠธ ์ƒ์„ฑ
490
+ const checkpointCount = 3 + Math.floor(Math.random() * 3);
491
+ const checkpointSize = 30;
492
+
493
+ // ์žฅ์• ๋ฌผ ์ฃผ๋ณ€์„ ํ†ต๊ณผํ•ด์•ผ ํ•˜๋Š” ์œ„์น˜ ์ƒ์„ฑ
494
+ const possiblePositions = [
495
+ { x: 700, y: 100 }, // ์šฐ์ธก ์ƒ๋‹จ
496
+ { x: 600, y: 400 }, // ์šฐ์ธก ํ•˜๋‹จ ์ค‘์•™
497
+ { x: 300, y: 400 }, // ํ•˜๋‹จ ์ค‘์•™
498
+ { x: 100, y: 300 }, // ์ขŒ์ธก ์ค‘์•™
499
+ { x: 400, y: 100 }, // ์ƒ๋‹จ ์ค‘์•™
500
+ { x: 200, y: 200 }, // ์ขŒ์ธก ์ค‘์•™
501
+ { x: 600, y: 200 } // ์šฐ์ธก ์ค‘์•™
502
+ ];
503
+
504
+ // ์…”ํ”Œํ•˜๊ณ  ์ฒดํฌํฌ์ธํŠธ ์ˆ˜๋งŒํผ ์„ ํƒ
505
+ const shuffled = [...possiblePositions].sort(() => 0.5 - Math.random());
506
+ for (let i = 0; i < checkpointCount; i++) {
507
+ const pos = shuffled[i];
508
+ this.checkpoints.push({
509
+ x: pos.x,
510
+ y: pos.y,
511
+ width: checkpointSize,
512
+ height: checkpointSize
513
+ });
514
+ }
515
+
516
+ // ์‹œ์ž‘ ์œ„์น˜ ์„ค์ • (ํ•ญ์ƒ ์ขŒ์ธก, ์ˆ˜์ง ์œ„์น˜๋Š” ๋ฌด์ž‘์œ„)
517
+ this.startPosition = {
518
+ x: 100,
519
+ y: 100 + Math.random() * 300,
520
+ angle: 0
521
+ };
522
+ },
523
+
524
+ draw(ctx) {
525
+ // ๋ฒฝ ๊ทธ๋ฆฌ๊ธฐ
526
+ ctx.fillStyle = '#4a5568';
527
+ this.walls.forEach(wall => {
528
+ ctx.fillRect(wall.x, wall.y, wall.width, wall.height);
529
+ });
530
+
531
+ // ์ฒดํฌํฌ์ธํŠธ ๊ทธ๋ฆฌ๊ธฐ
532
+ ctx.fillStyle = 'rgba(74, 222, 128, 0.3)';
533
+ this.checkpoints.forEach((checkpoint, index) => {
534
+ ctx.fillRect(checkpoint.x, checkpoint.y, checkpoint.width, checkpoint.height);
535
+
536
+ // Add checkpoint number
537
+ ctx.fillStyle = 'white';
538
+ ctx.font = '12px Arial';
539
+ ctx.textAlign = 'center';
540
+ ctx.textBaseline = 'middle';
541
+ ctx.fillText((index + 1).toString(),
542
+ checkpoint.x + checkpoint.width/2,
543
+ checkpoint.y + checkpoint.height/2);
544
+
545
+ ctx.fillStyle = 'rgba(74, 222, 128, 0.3)';
546
+ });
547
+
548
+ // ์‹œ์ž‘ ์œ„์น˜ ๊ทธ๋ฆฌ๊ธฐ
549
+ ctx.fillStyle = 'rgba(96, 165, 250, 0.5)';
550
+ ctx.fillRect(this.startPosition.x - 15, this.startPosition.y - 25, 30, 50);
551
+
552
+ // Draw start flag
553
+ ctx.fillStyle = 'white';
554
+ ctx.font = '14px Arial';
555
+ ctx.textAlign = 'center';
556
+ ctx.fillText("START", this.startPosition.x, this.startPosition.y + 15);
557
+ }
558
+ };
559
+
560
+ // ์ž๋™์ฐจ ํด๋ž˜์Šค
561
+ class Car {
562
+ constructor(brain) {
563
+ this.reset();
564
+ this.brain = brain ? brain : new NeuralNetwork([5, 8, 2]);
565
+ this.fitness = 0;
566
+ this.checkpointIndex = 0;
567
+ this.sensors = [0, 0, 0, 0, 0]; // ์ „๋ฐฉ, ์ขŒ์ธก, ์šฐ์ธก, ์ขŒ์ „๋ฐฉ, ์šฐ์ „๋ฐฉ
568
+ this.sensorAngles = [0, -Math.PI/4, Math.PI/4, -Math.PI/8, Math.PI/8];
569
+ this.sensorLength = 100;
570
+ this.color = 'rgba(59, 130, 246, 0.8)';
571
+ this.isBest = false;
572
+ this.lastPosition = { x: 0, y: 0 };
573
+ this.stuckTime = 0; // ์ž๋™์ฐจ๊ฐ€ ์›€์ง์ด์ง€ ์•Š๋Š” ์‹œ๊ฐ„ ์ถ”์ 
574
+ }
575
+
576
+ reset() {
577
+ this.x = track.startPosition.x;
578
+ this.y = track.startPosition.y;
579
+ this.angle = track.startPosition.angle;
580
+ this.speed = 0;
581
+ this.maxSpeed = 10; // ์ตœ๋Œ€ ์†๋„ ์ฆ๊ฐ€
582
+ this.acceleration = 0.2; // ๊ฐ€์†๋„ ์ฆ๊ฐ€
583
+ this.rotationSpeed = 0.1; // ํšŒ์ „ ์†๋„ ์ฆ๊ฐ€
584
+ this.damaged = false;
585
+ this.checkpointIndex = 0;
586
+ this.fitness = 0;
587
+ this.stuckTime = 0;
588
+ this.lastPosition = { x: this.x, y: this.y };
589
+ }
590
+
591
+ update(dt) {
592
+ if (this.damaged) return;
593
+
594
+ // ์ด์ „ ์œ„์น˜ ์ €์žฅ
595
+ this.lastPosition = { x: this.x, y: this.y };
596
+
597
+ // ์„ผ์„œ ์—…๋ฐ์ดํŠธ
598
+ this.updateSensors();
599
+
600
+ // ์‹ ๊ฒฝ๋ง ์ถœ๋ ฅ ๊ฐ€์ ธ์˜ค๊ธฐ
601
+ const outputs = this.brain.predict(this.sensors);
602
+
603
+ // ์กฐํ–ฅ ์ ์šฉ (outputs[0] = ์™ผ์ชฝ, outputs[1] = ์˜ค๋ฅธ์ชฝ)
604
+ const steering = outputs[1] - outputs[0]; // -1์—์„œ 1 ์‚ฌ์ด
605
+ this.angle += steering * this.rotationSpeed * dt;
606
+
607
+ // ์†๋„์™€ ์œ„์น˜ ์—…๋ฐ์ดํŠธ
608
+ this.speed = this.maxSpeed;
609
+ this.x += Math.sin(this.angle) * this.speed * dt;
610
+ this.y -= Math.cos(this.angle) * this.speed * dt;
611
+
612
+ // ์ถฉ๋Œ ๊ฒ€์‚ฌ
613
+ this.checkCollisions();
614
+
615
+ // ์ฒดํฌํฌ์ธํŠธ ๊ฒ€์‚ฌ
616
+ this.checkCheckpoints();
617
+
618
+ // ์ •์ง€ ํ™•์ธ (์ž๋™์ฐจ๊ฐ€ ์›€์ง์ด์ง€ ์•Š๋Š” ๊ฒฝ์šฐ)
619
+ const distance = Math.sqrt(
620
+ Math.pow(this.x - this.lastPosition.x, 2) +
621
+ Math.pow(this.y - this.lastPosition.y, 2)
622
+ );
623
+
624
+ if (distance < 0.5 * dt) {
625
+ this.stuckTime += dt;
626
+ if (this.stuckTime > 1.5) { // ์ •์ง€ ํŒ๋‹จ ์‹œ๊ฐ„ ๋‹จ์ถ• (3์ดˆ โ†’ 1.5์ดˆ)
627
+ this.damaged = true;
628
+ }
629
+ } else {
630
+ this.stuckTime = 0;
631
+
632
+ // ์ ํ•ฉ๋„ ์—…๋ฐ์ดํŠธ
633
+ this.fitness += distance; // ์ด๋™ ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ฅธ ์ ํ•ฉ๋„
634
+ }
635
+ }
636
+
637
+ updateSensors() {
638
+ this.sensors = this.sensorAngles.map(angle => {
639
+ const sensorAngle = this.angle + angle;
640
+ let sensorEndX = this.x + Math.sin(sensorAngle) * this.sensorLength;
641
+ let sensorEndY = this.y - Math.cos(sensorAngle) * this.sensorLength;
642
+
643
+ let minDistance = this.sensorLength;
644
+
645
+ // ๋ชจ๋“  ๋ฒฝ์— ๋Œ€ํ•ด ๊ฒ€์‚ฌ
646
+ for (const wall of track.walls) {
647
+ const intersection = this.lineRectIntersection(
648
+ this.x, this.y, sensorEndX, sensorEndY,
649
+ wall.x, wall.y, wall.width, wall.height
650
+ );
651
+
652
+ if (intersection) {
653
+ const distance = Math.sqrt(
654
+ Math.pow(intersection.x - this.x, 2) +
655
+ Math.pow(intersection.y - this.y, 2)
656
+ );
657
+
658
+ minDistance = Math.min(minDistance, distance);
659
+ }
660
+ }
661
+
662
+ // ์ •๊ทœํ™”๋œ ๊ฑฐ๋ฆฌ ๋ฐ˜ํ™˜ (0-1 ๋ฒ”์œ„; 1 = ์žฅ์• ๋ฌผ ์—†์Œ, 0 = ์ž๋™์ฐจ ๋ฐ”๋กœ ์•ž์— ์žฅ์• ๋ฌผ)
663
+ return 1 - (minDistance / this.sensorLength);
664
+ });
665
+ }
666
+
667
+ lineRectIntersection(x1, y1, x2, y2, rx, ry, rw, rh) {
668
+ // ์ง์„ ์ด ์ง์‚ฌ๊ฐํ˜•์˜ ๋ณ€๊ณผ ๊ต์ฐจํ•˜๋Š”์ง€ ํ™•์ธ
669
+ const left = this.lineLineIntersection(x1, y1, x2, y2, rx, ry, rx, ry + rh);
670
+ const right = this.lineLineIntersection(x1, y1, x2, y2, rx + rw, ry, rx + rw, ry + rh);
671
+ const top = this.lineLineIntersection(x1, y1, x2, y2, rx, ry, rx + rw, ry);
672
+ const bottom = this.lineLineIntersection(x1, y1, x2, y2, rx, ry + rh, rx + rw, ry + rh);
673
+
674
+ let closestIntersection = null;
675
+ let minDistance = Infinity;
676
+
677
+ [left, right, top, bottom].forEach(intersection => {
678
+ if (intersection) {
679
+ const distance = Math.sqrt(Math.pow(intersection.x - x1, 2) + Math.pow(intersection.y - y1, 2));
680
+ if (distance < minDistance) {
681
+ minDistance = distance;
682
+ closestIntersection = intersection;
683
+ }
684
+ }
685
+ });
686
+
687
+ return closestIntersection;
688
+ }
689
+
690
+ lineLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
691
+ // ๋‘ ์ง์„  ์‚ฌ์ด์˜ ๊ต์ฐจ์  ๊ณ„์‚ฐ
692
+ const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
693
+
694
+ if (denominator === 0) return null; // ์ง์„ ์ด ํ‰ํ–‰
695
+
696
+ const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
697
+ const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
698
+
699
+ if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
700
+ return {
701
+ x: x1 + ua * (x2 - x1),
702
+ y: y1 + ua * (y2 - y1)
703
+ };
704
+ }
705
+
706
+ return null;
707
+ }
708
+
709
+ checkCollisions() {
710
+ // ๋ฒฝ๊ณผ์˜ ์ถฉ๋Œ ๊ฐ์ง€ (๊ฐœ์„ ๋œ ๊ณ„์‚ฐ)
711
+ // ์ž๋™์ฐจ๋ฅผ ๋‹ค๊ฐํ˜•์œผ๋กœ ํ‘œํ˜„ํ•˜์—ฌ ๋” ์ •ํ™•ํ•œ ์ถฉ๋Œ ๊ฐ์ง€
712
+ const carCorners = this.getCarCorners();
713
+
714
+ // ๋ชจ๋“  ๋ฒฝ์— ๋Œ€ํ•ด ๊ฒ€์‚ฌ
715
+ for (const wall of track.walls) {
716
+ // ๊ฐ„๋‹จํ•œ ์‚ฌ๊ฐํ˜• ์ถฉ๋Œ ํ™•์ธ (์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ์ฒซ ๋‹จ๊ณ„)
717
+ if (this.x > wall.x - 10 && this.x < wall.x + wall.width + 10 &&
718
+ this.y > wall.y - 10 && this.y < wall.y + wall.height + 10) {
719
+
720
+ // ์ž๋™์ฐจ ๊ผญ์ง€์ ์ด ๋ฒฝ ๋‚ด๋ถ€์— ์žˆ๋Š”์ง€ ํ™•์ธ
721
+ for (const corner of carCorners) {
722
+ if (corner.x > wall.x && corner.x < wall.x + wall.width &&
723
+ corner.y > wall.y && corner.y < wall.y + wall.height) {
724
+ this.damaged = true;
725
+ return;
726
+ }
727
+ }
728
+ }
729
+ }
730
+
731
+ // ๊ฒฝ๊ณ„ ํ™•์ธ
732
+ if (this.x < 0 || this.x > canvas.width || this.y < 0 || this.y > canvas.height) {
733
+ this.damaged = true;
734
+ }
735
+ }
736
+
737
+ getCarCorners() {
738
+ // ์ž๋™์ฐจ์˜ ๋„ค ๊ผญ์ง€์  ๊ณ„์‚ฐ
739
+ const width = 12;
740
+ const height = 20;
741
+ const cornerOffsets = [
742
+ { x: -width/2, y: -height/2 }, // ์ขŒ์ƒ๋‹จ
743
+ { x: width/2, y: -height/2 }, // ์šฐ์ƒ๋‹จ
744
+ { x: width/2, y: height/2 }, // ์šฐํ•˜๋‹จ
745
+ { x: -width/2, y: height/2 } // ์ขŒํ•˜๋‹จ
746
+ ];
747
+
748
+ return cornerOffsets.map(offset => {
749
+ const rotatedX = offset.x * Math.cos(this.angle) - offset.y * Math.sin(this.angle);
750
+ const rotatedY = offset.x * Math.sin(this.angle) + offset.y * Math.cos(this.angle);
751
+ return {
752
+ x: this.x + rotatedX,
753
+ y: this.y + rotatedY
754
+ };
755
+ });
756
+ }
757
+
758
+ checkCheckpoints() {
759
+ if (this.checkpointIndex >= track.checkpoints.length) return;
760
+
761
+ const checkpoint = track.checkpoints[this.checkpointIndex];
762
+ if (this.x > checkpoint.x && this.x < checkpoint.x + checkpoint.width &&
763
+ this.y > checkpoint.y && this.y < checkpoint.y + checkpoint.height) {
764
+ this.checkpointIndex++;
765
+ this.fitness += 1000; // Bonus for reaching checkpoint
766
+
767
+ // Update best progress visualization
768
+ const progress = this.checkpointIndex / track.checkpoints.length;
769
+ if (progress > bestCarProgress) {
770
+ bestCarProgress = progress;
771
+ bestProgressBar.style.width = `${progress * 100}%`;
772
+
773
+ // Add confetti effect for completed checkpoints
774
+ if (this.checkpointIndex > 0) {
775
+ createConfetti(10, this.x, this.y);
776
+ }
777
+ }
778
+ }
779
+ }
780
+
781
+ draw(ctx) {
782
+ if (this.damaged) return;
783
+
784
+ ctx.save();
785
+ ctx.translate(this.x, this.y);
786
+ ctx.rotate(this.angle);
787
+
788
+ // Draw car body - Enhanced car shape with emoji style
789
+ if (this.isBest) {
790
+ // Draw fancy car for the best performer
791
+ ctx.fillStyle = 'rgba(220, 38, 38, 0.9)';
792
+
793
+ // Main body
794
+ ctx.beginPath();
795
+ ctx.roundRect(-6, -10, 12, 20, 2);
796
+ ctx.fill();
797
+
798
+ // Wheels
799
+ ctx.fillStyle = '#000';
800
+ ctx.fillRect(-7, -8, 2, 4); // left front
801
+ ctx.fillRect(5, -8, 2, 4); // right front
802
+ ctx.fillRect(-7, 4, 2, 4); // left rear
803
+ ctx.fillRect(5, 4, 2, 4); // right rear
804
+
805
+ // Windshield
806
+ ctx.fillStyle = '#60a5fa';
807
+ ctx.beginPath();
808
+ ctx.roundRect(-4, -8, 8, 6, 1);
809
+ ctx.fill();
810
+
811
+ // Draw a small crown on top
812
+ ctx.fillStyle = '#facc15';
813
+ ctx.beginPath();
814
+ ctx.moveTo(-3, -11);
815
+ ctx.lineTo(-1, -13);
816
+ ctx.lineTo(1, -11);
817
+ ctx.lineTo(3, -13);
818
+ ctx.lineTo(3, -10);
819
+ ctx.lineTo(-3, -10);
820
+ ctx.fill();
821
+ } else {
822
+ // Regular car
823
+ ctx.fillStyle = this.color;
824
+
825
+ // Main body
826
+ ctx.beginPath();
827
+ ctx.roundRect(-6, -10, 12, 20, 2);
828
+ ctx.fill();
829
+
830
+ // Wheels (simple)
831
+ ctx.fillStyle = '#000';
832
+ ctx.fillRect(-7, -7, 2, 3); // left front
833
+ ctx.fillRect(5, -7, 2, 3); // right front
834
+ ctx.fillRect(-7, 4, 2, 3); // left rear
835
+ ctx.fillRect(5, 4, 2, 3); // right rear
836
+
837
+ // Simple windshield
838
+ ctx.fillStyle = '#a3e0ff';
839
+ ctx.fillRect(-4, -7, 8, 5);
840
+ }
841
+
842
+ // ์ตœ๊ณ  ์ž๋™์ฐจ์˜ ์„ผ์„œ ๊ทธ๋ฆฌ๊ธฐ
843
+ if (this.isBest) {
844
+ ctx.restore(); // ์ปจํ…์ŠคํŠธ ๋ณต์›
845
+
846
+ // ์„ผ์„œ ๋ ˆ์ด ๊ทธ๋ฆฌ๊ธฐ
847
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
848
+ ctx.lineWidth = 1;
849
+
850
+ this.sensorAngles.forEach((angle, i) => {
851
+ const sensorAngle = this.angle + angle;
852
+ const sensorValue = this.sensors[i];
853
+ const sensorLength = this.sensorLength * (1 - sensorValue);
854
+
855
+ const endX = this.x + Math.sin(sensorAngle) * sensorLength;
856
+ const endY = this.y - Math.cos(sensorAngle) * sensorLength;
857
+
858
+ ctx.beginPath();
859
+ ctx.moveTo(this.x, this.y);
860
+ ctx.lineTo(endX, endY);
861
+ ctx.stroke();
862
+ });
863
+
864
+ return; // ์ด๋ฏธ ctx.restore()๋ฅผ ํ˜ธ์ถœํ–ˆ์œผ๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ ์ข…๋ฃŒ
865
+ }
866
+
867
+ ctx.restore();
868
+ }
869
+
870
+ clone() {
871
+ return new Car(this.brain.clone());
872
+ }
873
+ }
874
+
875
+ // ์‹ ๊ฒฝ๋ง ํด๋ž˜์Šค
876
+ class NeuralNetwork {
877
+ constructor(neuronCounts) {
878
+ this.levels = [];
879
+ for (let i = 0; i < neuronCounts.length - 1; i++) {
880
+ this.levels.push(new Level(
881
+ neuronCounts[i], neuronCounts[i + 1]
882
+ ));
883
+ }
884
+ }
885
+
886
+ predict(givenInputs) {
887
+ let outputs = Level.feedForward(
888
+ givenInputs, this.levels[0]
889
+ );
890
+
891
+ for (let i = 1; i < this.levels.length; i++) {
892
+ outputs = Level.feedForward(
893
+ outputs, this.levels[i]
894
+ );
895
+ }
896
+
897
+ return outputs;
898
+ }
899
+
900
+ clone() {
901
+ const clone = new NeuralNetwork([]);
902
+ clone.levels = this.levels.map(level => level.clone());
903
+ return clone;
904
+ }
905
+
906
+ mutate(rate) {
907
+ for (const level of this.levels) {
908
+ for (let i = 0; i < level.biases.length; i++) {
909
+ if (Math.random() < rate) {
910
+ level.biases[i] = lerp(
911
+ level.biases[i],
912
+ Math.random() * 2 - 1,
913
+ 0.5
914
+ );
915
+ }
916
+ }
917
+ for (let i = 0; i < level.weights.length; i++) {
918
+ for (let j = 0; j < level.weights[i].length; j++) {
919
+ if (Math.random() < rate) {
920
+ level.weights[i][j] = lerp(
921
+ level.weights[i][j],
922
+ Math.random() * 2 - 1,
923
+ 0.5
924
+ );
925
+ }
926
+ }
927
+ }
928
+ }
929
+ }
930
+
931
+ static crossover(parentA, parentB) {
932
+ // ๋‘ ๋ถ€๋ชจ ์‹ ๊ฒฝ๋ง์—์„œ ์ƒˆ ์‹ ๊ฒฝ๋ง ์ƒ์„ฑ
933
+ if (parentA.levels.length !== parentB.levels.length) {
934
+ console.error("๋ถ€๋ชจ ์‹ ๊ฒฝ๋ง ๊ตฌ์กฐ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค!");
935
+ return parentA.clone();
936
+ }
937
+
938
+ const childNetwork = new NeuralNetwork([]);
939
+ childNetwork.levels = [];
940
+
941
+ for (let l = 0; l < parentA.levels.length; l++) {
942
+ const levelA = parentA.levels[l];
943
+ const levelB = parentB.levels[l];
944
+
945
+ if (levelA.inputs.length !== levelB.inputs.length ||
946
+ levelA.outputs.length !== levelB.outputs.length) {
947
+ console.error("๋ถ€๋ชจ ๋ ˆ๋ฒจ ๊ตฌ์กฐ๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค!");
948
+ return parentA.clone();
949
+ }
950
+
951
+ const childLevel = new Level(levelA.inputs.length, levelA.outputs.length);
952
+
953
+ // ๊ต์ฐจ์  ์„ ํƒ (๋‹จ์ผ์  ๊ต์ฐจ)
954
+ const biasesSwitch = Math.floor(Math.random() * levelA.biases.length);
955
+
956
+ // ๋ฐ”์ด์–ด์Šค ๊ต์ฐจ
957
+ for (let i = 0; i < childLevel.biases.length; i++) {
958
+ childLevel.biases[i] = i < biasesSwitch
959
+ ? levelA.biases[i]
960
+ : levelB.biases[i];
961
+ }
962
+
963
+ // ๊ฐ€์ค‘์น˜ ๊ต์ฐจ
964
+ for (let i = 0; i < childLevel.weights.length; i++) {
965
+ const weightSwitch = Math.floor(Math.random() * levelA.weights[i].length);
966
+
967
+ for (let j = 0; j < childLevel.weights[i].length; j++) {
968
+ childLevel.weights[i][j] = j < weightSwitch
969
+ ? levelA.weights[i][j]
970
+ : levelB.weights[i][j];
971
+ }
972
+ }
973
+
974
+ childNetwork.levels.push(childLevel);
975
+ }
976
+
977
+ return childNetwork;
978
+ }
979
+
980
+ toJSON() {
981
+ return {
982
+ levels: this.levels.map(level => ({
983
+ inputs: level.inputs,
984
+ outputs: level.outputs,
985
+ biases: level.biases,
986
+ weights: level.weights
987
+ }))
988
+ };
989
+ }
990
+
991
+ static fromJSON(data) {
992
+ const network = new NeuralNetwork([]);
993
+ network.levels = data.levels.map(levelData => {
994
+ const level = new Level(levelData.inputs.length, levelData.outputs.length);
995
+ level.inputs = [...levelData.inputs];
996
+ level.outputs = [...levelData.outputs];
997
+ level.biases = [...levelData.biases];
998
+ level.weights = levelData.weights.map(w => [...w]);
999
+ return level;
1000
+ });
1001
+ return network;
1002
+ }
1003
+ }
1004
+
1005
+ function lerp(a, b, t) {
1006
+ return a + (b - a) * t;
1007
+ }
1008
+
1009
+ class Level {
1010
+ constructor(inputCount, outputCount) {
1011
+ this.inputs = new Array(inputCount);
1012
+ this.outputs = new Array(outputCount);
1013
+ this.biases = new Array(outputCount);
1014
+ this.weights = [];
1015
+
1016
+ for (let i = 0; i < inputCount; i++) {
1017
+ this.weights[i] = new Array(outputCount);
1018
+ }
1019
+
1020
+ Level.#randomize(this);
1021
+ }
1022
+
1023
+ static #randomize(level) {
1024
+ for (let i = 0; i < level.inputs.length; i++) {
1025
+ for (let j = 0; j < level.outputs.length; j++) {
1026
+ level.weights[i][j] = Math.random() * 2 - 1;
1027
+ }
1028
+ }
1029
+
1030
+ for (let i = 0; i < level.biases.length; i++) {
1031
+ level.biases[i] = Math.random() * 2 - 1;
1032
+ }
1033
+ }
1034
+
1035
+ static feedForward(givenInputs, level) {
1036
+ // ์ž…๋ ฅ ๊ฐ’ ์„ค์ •
1037
+ for (let i = 0; i < level.inputs.length; i++) {
1038
+ level.inputs[i] = givenInputs[i];
1039
+ }
1040
+
1041
+ // ๊ฐ ์ถœ๋ ฅ ๋‰ด๋Ÿฐ์— ๋Œ€ํ•ด ๊ฐ€์ค‘ ํ•ฉ๊ณ„ ๊ณ„์‚ฐ
1042
+ for (let i = 0; i < level.outputs.length; i++) {
1043
+ let sum = 0;
1044
+ for (let j = 0; j < level.inputs.length; j++) {
1045
+ sum += level.inputs[j] * level.weights[j][i];
1046
+ }
1047
+
1048
+ // Sigmoid ํ™œ์„ฑํ™” ํ•จ์ˆ˜ ์ ์šฉ
1049
+ level.outputs[i] = sigmoid(sum - level.biases[i]);
1050
+ }
1051
+
1052
+ return level.outputs;
1053
+ }
1054
+
1055
+ clone() {
1056
+ const clone = new Level(this.inputs.length, this.outputs.length);
1057
+ clone.inputs = [...this.inputs];
1058
+ clone.outputs = [...this.outputs];
1059
+ clone.biases = [...this.biases];
1060
+ clone.weights = this.weights.map(arr => [...arr]);
1061
+ return clone;
1062
+ }
1063
+ }
1064
+
1065
+ // ์œ ์ „ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ•จ์ˆ˜
1066
+ function nextGeneration() {
1067
+ generation++;
1068
+ generationCount.textContent = generation;
1069
+
1070
+ // ์ ํ•ฉ๋„ ๊ณ„์‚ฐ
1071
+ calculateFitness();
1072
+
1073
+ // ์ƒˆ ์ธ๊ตฌ ์ƒ์„ฑ
1074
+ const newPopulation = [];
1075
+
1076
+ // ์ ์‘ํ˜• ๋Œ์—ฐ๋ณ€์ด์œจ ์ ์šฉ
1077
+ const progressRate = bestCarProgress;
1078
+ const adaptedRate = mutationRate * (1 - progressRate * 0.5);
1079
+ mutationRate = Math.max(0.01, adaptedRate); // ์ตœ์†Œ 1%
1080
+ mutationValue.textContent = `${Math.round(mutationRate * 100)}%`;
1081
+ mutationSlider.value = Math.round(mutationRate * 100);
1082
+
1083
+ // ์ด์ „ ์„ธ๋Œ€์—์„œ ์ตœ๊ณ  ์ž๋™์ฐจ ์ถ”๊ฐ€ (์—˜๋ฆฌํ‹ฐ์ฆ˜)
1084
+ const eliteCount = Math.max(1, Math.floor(populationSize * 0.05)); // 5% ์—˜๋ฆฌํŠธ
1085
+ const eliteCars = getTopCars(eliteCount);
1086
+
1087
+ for (const eliteCar of eliteCars) {
1088
+ eliteCar.isBest = eliteCar === eliteCars[0];
1089
+ newPopulation.push(eliteCar.clone());
1090
+ }
1091
+
1092
+ // ๊ต์ฐจ์™€ ๋Œ์—ฐ๋ณ€์ด๋กœ ๋‚˜๋จธ์ง€ ์ฑ„์šฐ๊ธฐ
1093
+ while (newPopulation.length < populationSize) {
1094
+ if (Math.random() < 0.7 && newPopulation.length + 1 < populationSize) {
1095
+ // ๊ต์ฐจ
1096
+ const parentA = selectParent();
1097
+ const parentB = selectParent();
1098
+ const child = new Car(NeuralNetwork.crossover(parentA.brain, parentB.brain));
1099
+
1100
+ // ์ž์‹์—๊ฒŒ ์•ฝ๊ฐ„์˜ ๋Œ์—ฐ๋ณ€์ด ์ ์šฉ
1101
+ child.brain.mutate(mutationRate);
1102
+ newPopulation.push(child);
1103
+ } else {
1104
+ // ๋Œ์—ฐ๋ณ€์ด๋งŒ
1105
+ const parent = selectParent();
1106
+ const child = parent.clone();
1107
+ child.brain.mutate(mutationRate);
1108
+ newPopulation.push(child);
1109
+ }
1110
+ }
1111
+
1112
+ // ์ด์ „ ์ธ๊ตฌ ๊ต์ฒด
1113
+ cars = newPopulation;
1114
+
1115
+ // ์ž๋™์ฐจ ์ดˆ๊ธฐํ™”
1116
+ cars.forEach(car => car.reset());
1117
+
1118
+ // ์ง„ํ–‰๋ฅ  ์ดˆ๊ธฐํ™”
1119
+ bestCarProgress = 0;
1120
+ bestProgressBar.style.width = '0%';
1121
+ }
1122
+
1123
+ function calculateFitness() {
1124
+ let sum = 0;
1125
+ let max = 0;
1126
+
1127
+ cars.forEach(car => {
1128
+ // ์ฒดํฌํฌ์ธํŠธ ๋‹ฌ์„ฑ ๋ณด๋„ˆ์Šค
1129
+ car.fitness += car.checkpointIndex * 500;
1130
+
1131
+ sum += car.fitness;
1132
+ if (car.fitness > max) max = car.fitness;
1133
+ });
1134
+
1135
+ // ์ ํ•ฉ๋„ ์ •๊ทœํ™”
1136
+ cars.forEach(car => {
1137
+ car.fitness = car.fitness / sum;
1138
+ });
1139
+
1140
+ // UI ์—…๋ฐ์ดํŠธ
1141
+ maxFitness.textContent = Math.round(max);
1142
+ }
1143
+
1144
+ function getTopCars(count) {
1145
+ return [...cars]
1146
+ .sort((a, b) => b.fitness - a.fitness)
1147
+ .slice(0, count);
1148
+ }
1149
+
1150
+ function getBestCar() {
1151
+ let bestCar = cars[0];
1152
+ let bestFitness = cars[0].fitness;
1153
+
1154
+ for (let i = 1; i < cars.length; i++) {
1155
+ if (cars[i].fitness > bestFitness) {
1156
+ bestFitness = cars[i].fitness;
1157
+ bestCar = cars[i];
1158
+ }
1159
+ }
1160
+
1161
+ return bestCar;
1162
+ }
1163
+
1164
+ function selectParent() {
1165
+ // ๋ฃฐ๋ › ํœ  ์„ ํƒ
1166
+ let index = 0;
1167
+ let r = Math.random();
1168
+
1169
+ while (r > 0 && index < cars.length) {
1170
+ r -= cars[index].fitness;
1171
+ index++;
1172
+ }
1173
+
1174
+ index = Math.min(cars.length - 1, Math.max(0, index - 1));
1175
+ return cars[index];
1176
+ }
1177
+
1178
+ // ๋ชจ๋ธ ์ €์žฅ/๋ถˆ๋Ÿฌ์˜ค๊ธฐ ํ•จ์ˆ˜
1179
+ function saveBestModel() {
1180
+ const bestCar = getBestCar();
1181
+ if (bestCar) {
1182
+ try {
1183
+ const modelData = {
1184
+ brain: bestCar.brain.toJSON(),
1185
+ fitness: bestCar.fitness,
1186
+ generation: generation,
1187
+ timestamp: new Date().toISOString()
1188
+ };
1189
+
1190
+ localStorage.setItem('bestCarModel', JSON.stringify(modelData));
1191
+ return true;
1192
+ } catch (error) {
1193
+ console.error('Error saving model:', error);
1194
+ return false;
1195
+ }
1196
+ }
1197
+ return false;
1198
+ }
1199
+
1200
+ function loadModel() {
1201
+ try {
1202
+ const savedModel = localStorage.getItem('bestCarModel');
1203
+ if (savedModel) {
1204
+ const modelData = JSON.parse(savedModel);
1205
+
1206
+ // Use saved model for new population
1207
+ const newPopulation = [];
1208
+
1209
+ // Create best car with restored brain
1210
+ const restoredBrain = NeuralNetwork.fromJSON(modelData.brain);
1211
+ const bestCar = new Car(restoredBrain);
1212
+ bestCar.isBest = true;
1213
+ newPopulation.push(bestCar);
1214
+
1215
+ // Create variants from this model to fill population
1216
+ for (let i = 1; i < populationSize; i++) {
1217
+ const car = bestCar.clone();
1218
+ car.brain.mutate(mutationRate);
1219
+ newPopulation.push(car);
1220
+ }
1221
+
1222
+ // Replace population
1223
+ cars = newPopulation;
1224
+
1225
+ // Reset cars
1226
+ cars.forEach(car => car.reset());
1227
+
1228
+ // Create a celebratory confetti effect
1229
+ createConfetti(50, canvas.width/2, canvas.height/2);
1230
+
1231
+ return true;
1232
+ }
1233
+ } catch (error) {
1234
+ console.error('Error loading model:', error);
1235
+ }
1236
+ return false;
1237
+ }
1238
+
1239
+ // ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ƒํƒœ
1240
+ let cars = [];
1241
+ let animationId;
1242
+
1243
+ // ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ดˆ๊ธฐํ™”
1244
+ function init() {
1245
+ // ๋ฌด์ž‘์œ„ ํŠธ๋ž™ ์ƒ์„ฑ
1246
+ track.generateRandomTrack();
1247
+
1248
+ // ์ดˆ๊ธฐ ์ธ๊ตฌ ์ƒ์„ฑ
1249
+ cars = [];
1250
+ for (let i = 0; i < populationSize; i++) {
1251
+ cars.push(new Car());
1252
+ }
1253
+
1254
+ // ํ†ต๊ณ„ ์ดˆ๊ธฐํ™”
1255
+ generation = 0;
1256
+ generationCount.textContent = generation;
1257
+ populationCount.textContent = populationSize;
1258
+
1259
+ // ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹œ์ž‘
1260
+ isRunning = true;
1261
+ lastUpdateTime = performance.now();
1262
+ init();
1263
+ }
1264
+
1265
+ // ๊ฒฐ๊ณผ ์—…๋ฐ์ดํŠธ ์†๋„ ์ œํ•œ (๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ•˜์ง€ ์•Š๊ณ  10ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ)
1266
+ let updateFrameCount = 0;
1267
+
1268
+ // Confetti particle system
1269
+ const confetti = [];
1270
+
1271
+ function createConfetti(count, x, y) {
1272
+ for (let i = 0; i < count; i++) {
1273
+ confetti.push({
1274
+ x: x,
1275
+ y: y,
1276
+ size: 3 + Math.random() * 5,
1277
+ color: `hsl(${Math.random() * 360}, 100%, 70%)`,
1278
+ vx: -2 + Math.random() * 4,
1279
+ vy: -3 - Math.random() * 2,
1280
+ gravity: 0.1,
1281
+ life: 1, // 1 = full life, 0 = dead
1282
+ maxLife: 1 + Math.random()
1283
+ });
1284
+ }
1285
+ }
1286
+
1287
+ function updateConfetti(dt) {
1288
+ for (let i = confetti.length - 1; i >= 0; i--) {
1289
+ const particle = confetti[i];
1290
+
1291
+ // Update position
1292
+ particle.x += particle.vx * dt * 60;
1293
+ particle.y += particle.vy * dt * 60;
1294
+ particle.vy += particle.gravity * dt * 60;
1295
+
1296
+ // Update life
1297
+ particle.life -= 0.016 * dt * 60;
1298
+
1299
+ // Remove dead particles
1300
+ if (particle.life <= 0) {
1301
+ confetti.splice(i, 1);
1302
+ }
1303
+ }
1304
+ }
1305
+
1306
+ function drawConfetti(ctx) {
1307
+ for (const particle of confetti) {
1308
+ ctx.fillStyle = particle.color;
1309
+ ctx.globalAlpha = particle.life;
1310
+ ctx.fillRect(
1311
+ particle.x - particle.size/2,
1312
+ particle.y - particle.size/2,
1313
+ particle.size,
1314
+ particle.size
1315
+ );
1316
+ }
1317
+ ctx.globalAlpha = 1;
1318
+ }
1319
+
1320
+ // ์ฃผ์š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฃจํ”„
1321
+ function animate(currentTime = 0) {
1322
+ if (!isRunning) return;
1323
+
1324
+ animationId = requestAnimationFrame(animate);
1325
+
1326
+ // ๋ธํƒ€ ํƒ€์ž„ ๊ณ„์‚ฐ
1327
+ deltaTime = (currentTime - lastUpdateTime) / 1000; // ์ดˆ ๋‹จ์œ„
1328
+ lastUpdateTime = currentTime;
1329
+
1330
+ // ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์†๋„ ์ ์šฉ
1331
+ deltaTime *= simulationSpeed;
1332
+
1333
+ // ์ตœ๋Œ€ ๋ธํƒ€ ํƒ€์ž„ ์ œํ•œ (์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์•ˆ์ •์„ฑ์„ ์œ„ํ•ด)
1334
+ deltaTime = Math.min(deltaTime, 0.2);
1335
+
1336
+ // FPS ๊ณ„์‚ฐ (10ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์—…๋ฐ์ดํŠธ)
1337
+ frameCount++;
1338
+ updateFrameCount++;
1339
+ if (currentTime - lastFpsUpdate >= 1000) {
1340
+ fps = Math.round((frameCount * 1000) / (currentTime - lastFpsUpdate));
1341
+ if (updateFrameCount >= 10) {
1342
+ fpsCounter.textContent = fps;
1343
+ updateFrameCount = 0;
1344
+ }
1345
+ frameCount = 0;
1346
+ lastFpsUpdate = currentTime;
1347
+ }
1348
+
1349
+ // ์บ”๋ฒ„์Šค ์ง€์šฐ๊ธฐ
1350
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1351
+
1352
+ // ํŠธ๋ž™ ๊ทธ๋ฆฌ๊ธฐ
1353
+ track.draw(ctx);
1354
+
1355
+ // ์ž๋™์ฐจ ์—…๋ฐ์ดํŠธ ๋ฐ ๊ทธ๋ฆฌ๊ธฐ
1356
+ let alive = 0;
1357
+ cars.forEach(car => {
1358
+ car.update(deltaTime);
1359
+ car.draw(ctx);
1360
+ if (!car.damaged) alive++;
1361
+ });
1362
+
1363
+ // Draw confetti particles
1364
+ updateConfetti(deltaTime);
1365
+ drawConfetti(ctx);
1366
+
1367
+ aliveCount.textContent = alive;
1368
+
1369
+ // ๋ชจ๋“  ์ž๋™์ฐจ๊ฐ€ ์†์ƒ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ
1370
+ if (alive === 0) {
1371
+ nextGeneration();
1372
+ }
1373
+
1374
+ // ์ตœ๊ณ  ์ž๋™์ฐจ ๊ฐ•์กฐ ํ‘œ์‹œ
1375
+ const bestCar = getBestCar();
1376
+ if (bestCar) {
1377
+ bestCar.isBest = true;
1378
+ bestCar.color = 'rgba(220, 38, 38, 0.9)';
1379
+ }
1380
+ }
1381
+
1382
+ // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
1383
+ startBtn.addEventListener('click', () => {
1384
+ if (!isRunning) {
1385
+ isRunning = true;
1386
+ lastUpdateTime = performance.now();
1387
+ animate();
1388
+ }
1389
+ });
1390
+
1391
+ pauseBtn.addEventListener('click', () => {
1392
+ isRunning = false;
1393
+ cancelAnimationFrame(animationId);
1394
+ });
1395
+
1396
+ resetBtn.addEventListener('click', () => {
1397
+ isRunning = false;
1398
+ cancelAnimationFrame(animationId);
1399
+ init();
1400
+ });
1401
+
1402
+ saveBtn.addEventListener('click', () => {
1403
+ if (saveBestModel()) {
1404
+ alert('Model saved successfully!');
1405
+ } else {
1406
+ alert('Error saving model');
1407
+ }
1408
+ });
1409
+
1410
+ loadBtn.addEventListener('click', () => {
1411
+ if (loadModel()) {
1412
+ alert('Model loaded successfully!');
1413
+ } else {
1414
+ alert('No saved model found or error loading model');
1415
+ }
1416
+ });
1417
+
1418
+ // ์Šฌ๋ผ์ด๋” ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
1419
+ populationSlider.addEventListener('input', () => {
1420
+ populationSize = parseInt(populationSlider.value);
1421
+ populationValue.textContent = populationSize;
1422
+ populationCount.textContent = populationSize;
1423
+ });
1424
+
1425
+ mutationSlider.addEventListener('input', () => {
1426
+ mutationRate = parseInt(mutationSlider.value) / 100;
1427
+ mutationValue.textContent = `${parseInt(mutationSlider.value)}%`;
1428
+ });
1429
+
1430
+ speedSlider.addEventListener('input', () => {
1431
+ simulationSpeed = parseInt(speedSlider.value) * 3;
1432
+ speedValue.textContent = `${simulationSpeed}x`;
1433
+ });
1434
+
1435
+ // ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ดˆ๊ธฐํ™” ๋ฐ ์‹œ์ž‘
1436
+ init();
1437
+ });
1438
+ </script>
1439
+ </body>
1440
+ </html>