CoolFace
Apppublic

hackstone/Evolution-Simulation

sourceHugging Faceapache-2.0updated 1y agoView on Hugging Face
0likes
index.html1477 linesDownload Raw Back to root
1<!DOCTYPE html>2<html lang="en">3<head>4    <meta charset="UTF-8">5    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">6    <meta name="viewport" content="width=device-width, initial-scale=1.0">7    <title>AI Driving Simulation</title>8    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />9    <style>10        body {11            background-color: #111827;12            color: #f3f4f6;13            font-family: Arial, sans-serif;14            line-height: 1.5;15            margin: 0;16            padding: 20px;17        }18        19        .container {20            max-width: 1200px;21            margin: 0 auto;22        }23        24        h1 {25            color: #60a5fa;26            text-align: center;27            margin-bottom: 20px;28            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;29            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);30            letter-spacing: 1px;31        }32        33        canvas {34            background-color: #2d3748;35            border-radius: 12px;36            display: block;37            margin: 0 auto 20px;38            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1);39            border: 2px solid #4a5568;40        }41        42        .controls {43            display: flex;44            gap: 10px;45            margin-bottom: 20px;46            justify-content: center;47            flex-wrap: wrap;48        }49        50        button {51            background-color: #3b82f6;52            color: white;53            border: none;54            padding: 10px 18px;55            border-radius: 8px;56            cursor: pointer;57            font-weight: bold;58            transition: all 0.2s ease;59            display: flex;60            align-items: center;61            gap: 5px;62            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);63        }64        65        button:hover {66            background-color: #2563eb;67            transform: translateY(-2px);68            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);69        }70        71        button:active {72            transform: translateY(1px);73        }74        75        .start-btn {76            background-color: #10b981;77        }78        79        .start-btn:hover {80            background-color: #059669;81        }82        83        .pause-btn {84            background-color: #f59e0b;85        }86        87        .pause-btn:hover {88            background-color: #d97706;89        }90        91        .reset-btn {92            background-color: #ef4444;93        }94        95        .reset-btn:hover {96            background-color: #dc2626;97        }98        99        .stats {100            display: flex;101            justify-content: space-between;102            background-color: #1f2937;103            padding: 18px;104            border-radius: 12px;105            margin-bottom: 20px;106            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);107            flex-wrap: wrap;108            gap: 10px;109            border: 1px solid #374151;110        }111        112        .stat-item {113            text-align: center;114            background-color: #2d3748;115            padding: 10px 15px;116            border-radius: 8px;117            min-width: 90px;118            transition: all 0.3s ease;119        }120        121        .stat-item:hover {122            transform: translateY(-2px);123            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);124        }125        126        .stat-value {127            font-size: 1.4em;128            font-weight: bold;129            color: #60a5fa;130            margin-top: 5px;131        }132        133        .settings {134            background-color: #1f2937;135            padding: 20px;136            border-radius: 12px;137            margin-bottom: 20px;138            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);139            border: 1px solid #374151;140        }141        142        .slider-container {143            margin-bottom: 15px;144            background-color: #2d3748;145            padding: 12px;146            border-radius: 8px;147        }148        149        .slider-container label {150            display: block;151            margin-bottom: 8px;152            font-weight: 500;153            color: #d1d5db;154        }155        156        input[type="range"] {157            width: 100%;158            margin-bottom: 8px;159            height: 6px;160            -webkit-appearance: none;161            background: #4b5563;162            border-radius: 5px;163            outline: none;164        }165        166        input[type="range"]::-webkit-slider-thumb {167            -webkit-appearance: none;168            width: 18px;169            height: 18px;170            background: #3b82f6;171            border-radius: 50%;172            cursor: pointer;173            transition: background 0.2s;174        }175        176        input[type="range"]::-webkit-slider-thumb:hover {177            background: #2563eb;178        }179        180        .progress-container {181            background-color: #374151;182            height: 10px;183            border-radius: 5px;184            margin-top: 10px;185            overflow: hidden;186            box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);187        }188        189        .progress-bar {190            height: 100%;191            background: linear-gradient(90deg, #10b981, #3b82f6);192            width: 0;193            transition: width 0.3s;194            box-shadow: 0 0 5px rgba(16, 185, 129, 0.5);195        }196        197        /* Car emoji styling */198        .car-emoji {199            font-size: 1.2em;200            margin-right: 5px;201        }202        203        /* Added visual elements */204        .section-title {205            display: flex;206            align-items: center;207            margin-bottom: 15px;208            border-bottom: 1px solid #374151;209            padding-bottom: 8px;210        }211        212        .section-title i {213            margin-right: 8px;214            color: #60a5fa;215        }216        217        /* Custom checkbox */218        .toggle-switch {219            position: relative;220            display: inline-block;221            width: 50px;222            height: 24px;223        }224        225        .toggle-switch input {226            opacity: 0;227            width: 0;228            height: 0;229        }230        231        .toggle-slider {232            position: absolute;233            cursor: pointer;234            top: 0;235            left: 0;236            right: 0;237            bottom: 0;238            background-color: #4b5563;239            transition: .4s;240            border-radius: 24px;241        }242        243        .toggle-slider:before {244            position: absolute;245            content: "";246            height: 16px;247            width: 16px;248            left: 4px;249            bottom: 4px;250            background-color: white;251            transition: .4s;252            border-radius: 50%;253        }254        255        input:checked + .toggle-slider {256            background-color: #3b82f6;257        }258        259        input:checked + .toggle-slider:before {260            transform: translateX(26px);261        }262    </style>263</head>264<body>265    <div class="container">266        <h1><i class="fas fa-car-side"></i> Evolution Simulation</h1>267        268        <canvas id="simulationCanvas" width="800" height="500"></canvas>269        270        <div class="controls">271            <button id="startBtn" class="start-btn"><i class="fas fa-play"></i> Start</button>272            <button id="pauseBtn" class="pause-btn"><i class="fas fa-pause"></i> Pause</button>273            <button id="resetBtn" class="reset-btn"><i class="fas fa-sync-alt"></i> New Track</button>274            <button id="saveBtn"><i class="fas fa-save"></i> Save Model</button>275            <button id="loadBtn"><i class="fas fa-upload"></i> Load Model</button>276        </div>277        278        <div class="stats">279            <div class="stat-item">280                <div><i class="fas fa-dna"></i> Generation</div>281                <div id="generationCount" class="stat-value">0</div>282            </div>283            <div class="stat-item">284                <div><i class="fas fa-car"></i> Alive</div>285                <div class="stat-value"><span id="aliveCount">0</span>/<span id="populationCount">0</span></div>286            </div>287            <div class="stat-item">288                <div><i class="fas fa-trophy"></i> Best Fitness</div>289                <div id="maxFitness" class="stat-value">0</div>290            </div>291            <div class="stat-item">292                <div><i class="fas fa-tachometer-alt"></i> FPS</div>293                <div id="fpsCounter" class="stat-value">0</div>294            </div>295        </div>296        297        <div class="settings">298            <div class="section-title">299                <i class="fas fa-sliders-h"></i> <h3>Simulation Settings</h3>300            </div>301            302            <div class="slider-container">303                <label for="populationSlider"><i class="fas fa-users"></i> Population Size:</label>304                <input type="range" id="populationSlider" min="10" max="300" value="100">305                <span id="populationValue">100</span>306            </div>307            308            <div class="slider-container">309                <label for="mutationSlider"><i class="fas fa-random"></i> Mutation Rate:</label>310                <input type="range" id="mutationSlider" min="1" max="100" value="10">311                <span id="mutationValue">10%</span>312            </div>313            314            <div class="slider-container">315                <label for="speedSlider"><i class="fas fa-fast-forward"></i> Simulation Speed:</label>316                <input type="range" id="speedSlider" min="1" max="30" value="5">317                <span id="speedValue">15x</span>318            </div>319            320            <div class="slider-container">321                <label><i class="fas fa-flag-checkered"></i> Track Progress:</label>322                <div class="progress-container">323                    <div id="bestProgressBar" class="progress-bar"></div>324                </div>325            </div>326        </div>327        328        <div class="settings">329            <div class="section-title">330                <i class="fas fa-info-circle"></i> <h3>About This Simulation</h3>331            </div>332            <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>333            <p><strong>Key Improvements:</strong></p>334            <ul>335                <li><i class="fas fa-brain"></i> <strong>Enhanced Neural Network:</strong> Using Sigmoid activation function for smoother decision making</li>336                <li><i class="fas fa-random"></i> <strong>Crossover:</strong> Combining the best traits from parent models</li>337                <li><i class="fas fa-chart-line"></i> <strong>Adaptive Mutation:</strong> Automatically adjusts as generations progress</li>338                <li><i class="fas fa-bolt"></i> <strong>Performance Optimization:</strong> Delta-time based updates for consistent simulation</li>339                <li><i class="fas fa-exclamation-triangle"></i> <strong>Improved Collision Detection:</strong> More accurate polygon-based detection</li>340                <li><i class="fas fa-save"></i> <strong>Model Saving:</strong> Save and load your best models</li>341            </ul>342        </div>343    </div>344 345    <script>346        document.addEventListener('DOMContentLoaded', () => {347            // Add roundRect polyfill for browsers that don't support it348            if (!CanvasRenderingContext2D.prototype.roundRect) {349                CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, radius) {350                    if (typeof radius === 'undefined') {351                        radius = 5;352                    }353                    354                    this.beginPath();355                    this.moveTo(x + radius, y);356                    this.lineTo(x + width - radius, y);357                    this.arcTo(x + width, y, x + width, y + radius, radius);358                    this.lineTo(x + width, y + height - radius);359                    this.arcTo(x + width, y + height, x + width - radius, y + height, radius);360                    this.lineTo(x + radius, y + height);361                    this.arcTo(x, y + height, x, y + height - radius, radius);362                    this.lineTo(x, y + radius);363                    this.arcTo(x, y, x + radius, y, radius);364                    this.closePath();365                    return this;366                };367            }368            369            // Canvas 설정370            const canvas = document.getElementById('simulationCanvas');371            const ctx = canvas.getContext('2d');372            373            // UI 요소374            const startBtn = document.getElementById('startBtn');375            const pauseBtn = document.getElementById('pauseBtn');376            const resetBtn = document.getElementById('resetBtn');377            const saveBtn = document.getElementById('saveBtn');378            const loadBtn = document.getElementById('loadBtn');379            380            const populationSlider = document.getElementById('populationSlider');381            const mutationSlider = document.getElementById('mutationSlider');382            const speedSlider = document.getElementById('speedSlider');383            384            const populationValue = document.getElementById('populationValue');385            const mutationValue = document.getElementById('mutationValue');386            const speedValue = document.getElementById('speedValue');387            388            const generationCount = document.getElementById('generationCount');389            const aliveCount = document.getElementById('aliveCount');390            const populationCount = document.getElementById('populationCount');391            const maxFitness = document.getElementById('maxFitness');392            const fpsCounter = document.getElementById('fpsCounter');393            const bestProgressBar = document.getElementById('bestProgressBar');394            395            // 시뮬레이션 매개변수396            let populationSize = parseInt(populationSlider.value);397            let mutationRate = parseInt(mutationSlider.value) / 100;398            let simulationSpeed = parseInt(speedSlider.value) * 3; // 3배 빠른 속도399            let isRunning = false;400            let generation = 0;401            let fps = 0;402            let bestCarProgress = 0;403            let deltaTime = 0;404            let lastUpdateTime = 0;405            let frameCount = 0;406            let lastFpsUpdate = 0;407            408            // 활성화 함수409            const sigmoid = (x) => 1 / (1 + Math.exp(-x));410            const relu = (x) => Math.max(0, x);411            412            // 트랙 정의413            const track = {414                walls: [],415                checkpoints: [],416                startPosition: { x: 100, y: 250, angle: 0 },417                418                generateRandomTrack() {419                    this.walls = [];420                    this.checkpoints = [];421                    422                    // 외부 경계 벽 (항상 존재)423                    this.walls.push(424                        { x: 50, y: 50, width: 700, height: 20 }, // 상단425                        { x: 50, y: 50, width: 20, height: 400 }, // 좌측426                        { x: 50, y: 430, width: 700, height: 20 }, // 하단427                        { x: 730, y: 50, width: 20, height: 400 }  // 우측428                    );429                    430                    // 무작위 장애물 생성431                    const obstacleCount = 3 + Math.floor(Math.random() * 6);432                    for (let i = 0; i < obstacleCount; i++) {433                        const isVertical = Math.random() > 0.5;434                        let x, y, width, height;435                        436                        if (isVertical) {437                            width = 20;438                            height = 50 + Math.random() * 200;439                            x = 100 + Math.random() * 600;440                            y = 100 + Math.random() * (400 - height);441                        } else {442                            width = 50 + Math.random() * 200;443                            height = 20;444                            x = 100 + Math.random() * (700 - width);445                            y = 100 + Math.random() * 300;446                        }447                        448                        // 시작 위치를 막지 않도록 확인449                        if (!(x < 150 && y < 300 && y + height > 200)) {450                            this.walls.push({ x, y, width, height });451                        }452                    }453                    454                    // 체크포인트 생성455                    const checkpointCount = 3 + Math.floor(Math.random() * 3);456                    const checkpointSize = 30;457                    458                    // 장애물 주변을 통과해야 하는 위치 생성459                    const possiblePositions = [460                        { x: 700, y: 100 }, // 우측 상단461                        { x: 600, y: 400 }, // 우측 하단 중앙462                        { x: 300, y: 400 }, // 하단 중앙463                        { x: 100, y: 300 }, // 좌측 중앙464                        { x: 400, y: 100 }, // 상단 중앙465                        { x: 200, y: 200 }, // 좌측 중앙466                        { x: 600, y: 200 }  // 우측 중앙467                    ];468                    469                    // 셔플하고 체크포인트 수만큼 선택470                    const shuffled = [...possiblePositions].sort(() => 0.5 - Math.random());471                    for (let i = 0; i < checkpointCount; i++) {472                        const pos = shuffled[i];473                        this.checkpoints.push({474                            x: pos.x,475                            y: pos.y,476                            width: checkpointSize,477                            height: checkpointSize478                        });479                    }480                    481                    // 시작 위치 설정 (항상 좌측, 수직 위치는 무작위)482                    this.startPosition = {483                        x: 100,484                        y: 100 + Math.random() * 300,485                        angle: 0486                    };487                },488                489                draw(ctx) {490                    // 벽 그리기491                    ctx.fillStyle = '#4a5568';492                    this.walls.forEach(wall => {493                        ctx.fillRect(wall.x, wall.y, wall.width, wall.height);494                    });495                    496                    // 체크포인트 그리기497                    ctx.fillStyle = 'rgba(74, 222, 128, 0.3)';498                    this.checkpoints.forEach((checkpoint, index) => {499                        ctx.fillRect(checkpoint.x, checkpoint.y, checkpoint.width, checkpoint.height);500                        501                        // Add checkpoint number502                        ctx.fillStyle = 'white';503                        ctx.font = '12px Arial';504                        ctx.textAlign = 'center';505                        ctx.textBaseline = 'middle';506                        ctx.fillText((index + 1).toString(), 507                                    checkpoint.x + checkpoint.width/2, 508                                    checkpoint.y + checkpoint.height/2);509                        510                        ctx.fillStyle = 'rgba(74, 222, 128, 0.3)';511                    });512                    513                    // 시작 위치 그리기514                    ctx.fillStyle = 'rgba(96, 165, 250, 0.5)';515                    ctx.fillRect(this.startPosition.x - 15, this.startPosition.y - 25, 30, 50);516                    517                    // Draw start flag518                    ctx.fillStyle = 'white';519                    ctx.font = '14px Arial';520                    ctx.textAlign = 'center';521                    ctx.fillText("START", this.startPosition.x, this.startPosition.y + 15);522                }523            };524            525            // 자동차 클래스526            class Car {527                constructor(brain) {528                    this.reset();529                    this.brain = brain ? brain : new NeuralNetwork([5, 8, 2]);530                    this.fitness = 0;531                    this.checkpointIndex = 0;532                    this.sensors = [0, 0, 0, 0, 0]; // 전방, 좌측, 우측, 좌전방, 우전방533                    this.sensorAngles = [0, -Math.PI/4, Math.PI/4, -Math.PI/8, Math.PI/8];534                    this.sensorLength = 100;535                    this.color = 'rgba(59, 130, 246, 0.8)';536                    this.isBest = false;537                    this.lastPosition = { x: 0, y: 0 };538                    this.stuckTime = 0; // 자동차가 움직이지 않는 시간 추적539                }540                541                reset() {542                    this.x = track.startPosition.x;543                    this.y = track.startPosition.y;544                    this.angle = track.startPosition.angle;545                    this.speed = 0;546                    this.maxSpeed = 10; // 최대 속도 증가547                    this.acceleration = 0.2; // 가속도 증가548                    this.rotationSpeed = 0.1; // 회전 속도 증가549                    this.damaged = false;550                    this.checkpointIndex = 0;551                    this.fitness = 0;552                    this.stuckTime = 0;553                    this.lastPosition = { x: this.x, y: this.y };554                }555                556                update(dt) {557                    if (this.damaged) return;558                    559                    // 이전 위치 저장560                    this.lastPosition = { x: this.x, y: this.y };561                    562                    // 센서 업데이트563                    this.updateSensors();564                    565                    // 신경망 출력 가져오기566                    const outputs = this.brain.predict(this.sensors);567                    568                    // 조향 적용 (outputs[0] = 왼쪽, outputs[1] = 오른쪽)569                    const steering = outputs[1] - outputs[0]; // -1에서 1 사이570                    this.angle += steering * this.rotationSpeed * dt;571                    572                    // 속도와 위치 업데이트573                    this.speed = this.maxSpeed;574                    this.x += Math.sin(this.angle) * this.speed * dt;575                    this.y -= Math.cos(this.angle) * this.speed * dt;576                    577                    // 충돌 검사578                    this.checkCollisions();579                    580                    // 체크포인트 검사581                    this.checkCheckpoints();582                    583                    // 정지 확인 (자동차가 움직이지 않는 경우)584                    const distance = Math.sqrt(585                        Math.pow(this.x - this.lastPosition.x, 2) + 586                        Math.pow(this.y - this.lastPosition.y, 2)587                    );588                    589                    if (distance < 0.5 * dt) {590                        this.stuckTime += dt;591                        if (this.stuckTime > 1.5) { // 정지 판단 시간 단축 (3초 → 1.5초)592                            this.damaged = true;593                        }594                    } else {595                        this.stuckTime = 0;596                        597                        // 적합도 업데이트598                        this.fitness += distance; // 이동 거리에 따른 적합도599                    }600                }601                602                updateSensors() {603                    this.sensors = this.sensorAngles.map(angle => {604                        const sensorAngle = this.angle + angle;605                        let sensorEndX = this.x + Math.sin(sensorAngle) * this.sensorLength;606                        let sensorEndY = this.y - Math.cos(sensorAngle) * this.sensorLength;607                        608                        let minDistance = this.sensorLength;609                        610                        // 모든 벽에 대해 검사611                        for (const wall of track.walls) {612                            const intersection = this.lineRectIntersection(613                                this.x, this.y, sensorEndX, sensorEndY,614                                wall.x, wall.y, wall.width, wall.height615                            );616                            617                            if (intersection) {618                                const distance = Math.sqrt(619                                    Math.pow(intersection.x - this.x, 2) + 620                                    Math.pow(intersection.y - this.y, 2)621                                );622                                623                                minDistance = Math.min(minDistance, distance);624                            }625                        }626                        627                        // 정규화된 거리 반환 (0-1 범위; 1 = 장애물 없음, 0 = 자동차 바로 앞에 장애물)628                        return 1 - (minDistance / this.sensorLength);629                    });630                }631                632                lineRectIntersection(x1, y1, x2, y2, rx, ry, rw, rh) {633                    // 직선이 직사각형의 변과 교차하는지 확인634                    const left = this.lineLineIntersection(x1, y1, x2, y2, rx, ry, rx, ry + rh);635                    const right = this.lineLineIntersection(x1, y1, x2, y2, rx + rw, ry, rx + rw, ry + rh);636                    const top = this.lineLineIntersection(x1, y1, x2, y2, rx, ry, rx + rw, ry);637                    const bottom = this.lineLineIntersection(x1, y1, x2, y2, rx, ry + rh, rx + rw, ry + rh);638                    639                    let closestIntersection = null;640                    let minDistance = Infinity;641                    642                    [left, right, top, bottom].forEach(intersection => {643                        if (intersection) {644                            const distance = Math.sqrt(Math.pow(intersection.x - x1, 2) + Math.pow(intersection.y - y1, 2));645                            if (distance < minDistance) {646                                minDistance = distance;647                                closestIntersection = intersection;648                            }649                        }650                    });651                    652                    return closestIntersection;653                }654                655                lineLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {656                    // 두 직선 사이의 교차점 계산657                    const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);658                    659                    if (denominator === 0) return null; // 직선이 평행660                    661                    const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;662                    const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;663                    664                    if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {665                        return {666                            x: x1 + ua * (x2 - x1),667                            y: y1 + ua * (y2 - y1)668                        };669                    }670                    671                    return null;672                }673                674                checkCollisions() {675                    // 벽과의 충돌 감지 (개선된 계산)676                    // 자동차를 다각형으로 표현하여 더 정확한 충돌 감지677                    const carCorners = this.getCarCorners();678                    679                    // 모든 벽에 대해 검사680                    for (const wall of track.walls) {681                        // 간단한 사각형 충돌 확인 (최적화를 위한 첫 단계)682                        if (this.x > wall.x - 10 && this.x < wall.x + wall.width + 10 &&683                            this.y > wall.y - 10 && this.y < wall.y + wall.height + 10) {684                            685                            // 자동차 꼭지점이 벽 내부에 있는지 확인686                            for (const corner of carCorners) {687                                if (corner.x > wall.x && corner.x < wall.x + wall.width &&688                                    corner.y > wall.y && corner.y < wall.y + wall.height) {689                                    this.damaged = true;690                                    return;691                                }692                            }693                        }694                    }695                    696                    // 경계 확인697                    if (this.x < 0 || this.x > canvas.width || this.y < 0 || this.y > canvas.height) {698                        this.damaged = true;699                    }700                }701                702                getCarCorners() {703                    // 자동차의 네 꼭지점 계산704                    const width = 12;705                    const height = 20;706                    const cornerOffsets = [707                        { x: -width/2, y: -height/2 }, // 좌상단708                        { x: width/2, y: -height/2 },  // 우상단709                        { x: width/2, y: height/2 },   // 우하단710                        { x: -width/2, y: height/2 }   // 좌하단711                    ];712                    713                    return cornerOffsets.map(offset => {714                        const rotatedX = offset.x * Math.cos(this.angle) - offset.y * Math.sin(this.angle);715                        const rotatedY = offset.x * Math.sin(this.angle) + offset.y * Math.cos(this.angle);716                        return {717                            x: this.x + rotatedX,718                            y: this.y + rotatedY719                        };720                    });721                }722                723                checkCheckpoints() {724                    if (this.checkpointIndex >= track.checkpoints.length) return;725                    726                    const checkpoint = track.checkpoints[this.checkpointIndex];727                    if (this.x > checkpoint.x && this.x < checkpoint.x + checkpoint.width &&728                        this.y > checkpoint.y && this.y < checkpoint.y + checkpoint.height) {729                        this.checkpointIndex++;730                        this.fitness += 1000; // Bonus for reaching checkpoint731                        732                        // Update best progress visualization733                        const progress = this.checkpointIndex / track.checkpoints.length;734                        if (progress > bestCarProgress) {735                            bestCarProgress = progress;736                            bestProgressBar.style.width = `${progress * 100}%`;737                            738                            // Add confetti effect for completed checkpoints739                            if (this.checkpointIndex > 0) {740                                createConfetti(10, this.x, this.y);741                            }742                        }743                    }744                }745                746                draw(ctx) {747                    if (this.damaged) return;748                    749                    ctx.save();750                    ctx.translate(this.x, this.y);751                    ctx.rotate(this.angle);752                    753                    // Draw car body - Enhanced car shape with emoji style754                    if (this.isBest) {755                        // Draw fancy car for the best performer756                        ctx.fillStyle = 'rgba(220, 38, 38, 0.9)';757                        758                        // Main body - using rectangles instead of roundRect to avoid issues759                        ctx.fillRect(-6, -10, 12, 20);760                        761                        // Wheels762                        ctx.fillStyle = '#000';763                        ctx.fillRect(-7, -8, 2, 4); // left front764                        ctx.fillRect(5, -8, 2, 4);  // right front765                        ctx.fillRect(-7, 4, 2, 4);  // left rear766                        ctx.fillRect(5, 4, 2, 4);   // right rear767                        768                        // Windshield769                        ctx.fillStyle = '#60a5fa';770                        ctx.fillRect(-4, -8, 8, 6);771                        772                        // Draw a small crown on top773                        ctx.fillStyle = '#facc15';774                        ctx.beginPath();775                        ctx.moveTo(-3, -11);776                        ctx.lineTo(-1, -13);777                        ctx.lineTo(1, -11);778                        ctx.lineTo(3, -13);779                        ctx.lineTo(3, -10);780                        ctx.lineTo(-3, -10);781                        ctx.fill();782                    } else {783                        // Regular car784                        ctx.fillStyle = this.color;785                        786                        // Main body - using rectangles 787                        ctx.fillRect(-6, -10, 12, 20);788                        789                        // Wheels (simple)790                        ctx.fillStyle = '#000';791                        ctx.fillRect(-7, -7, 2, 3); // left front792                        ctx.fillRect(5, -7, 2, 3);  // right front793                        ctx.fillRect(-7, 4, 2, 3);  // left rear794                        ctx.fillRect(5, 4, 2, 3);   // right rear795                        796                        // Simple windshield797                        ctx.fillStyle = '#a3e0ff';798                        ctx.fillRect(-4, -7, 8, 5);799                    }800                    801                    // 최고 자동차의 센서 그리기802                    if (this.isBest) {803                        ctx.restore(); // 컨텍스트 복원804                        805                        // 센서 레이 그리기806                        ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';807                        ctx.lineWidth = 1;808                        809                        this.sensorAngles.forEach((angle, i) => {810                            const sensorAngle = this.angle + angle;811                            const sensorValue = this.sensors[i];812                            const sensorLength = this.sensorLength * (1 - sensorValue);813                            814                            const endX = this.x + Math.sin(sensorAngle) * sensorLength;815                            const endY = this.y - Math.cos(sensorAngle) * sensorLength;816                            817                            ctx.beginPath();818                            ctx.moveTo(this.x, this.y);819                            ctx.lineTo(endX, endY);820                            ctx.stroke();821                        });822                        823                        return; // 이미 ctx.restore()를 호출했으므로 여기서 종료824                    }825                    826                    ctx.restore();827                }828                829                clone() {830                    return new Car(this.brain.clone());831                }832            }833            834            // 신경망 클래스835            class NeuralNetwork {836                constructor(neuronCounts) {837                    this.levels = [];838                    for (let i = 0; i < neuronCounts.length - 1; i++) {839                        this.levels.push(new Level(840                            neuronCounts[i], neuronCounts[i + 1]841                        ));842                    }843                }844                845                predict(givenInputs) {846                    let outputs = Level.feedForward(847                        givenInputs, this.levels[0]848                    );849                    850                    for (let i = 1; i < this.levels.length; i++) {851                        outputs = Level.feedForward(852                            outputs, this.levels[i]853                        );854                    }855                    856                    return outputs;857                }858                859                clone() {860                    const clone = new NeuralNetwork([]);861                    clone.levels = this.levels.map(level => level.clone());862                    return clone;863                }864                865                mutate(rate) {866                    for (const level of this.levels) {867                        for (let i = 0; i < level.biases.length; i++) {868                            if (Math.random() < rate) {869                                level.biases[i] = lerp(870                                    level.biases[i],871                                    Math.random() * 2 - 1,872                                    0.5873                                );874                            }875                        }876                        for (let i = 0; i < level.weights.length; i++) {877                            for (let j = 0; j < level.weights[i].length; j++) {878                                if (Math.random() < rate) {879                                    level.weights[i][j] = lerp(880                                        level.weights[i][j],881                                        Math.random() * 2 - 1,882                                        0.5883                                    );884                                }885                            }886                        }887                    }888                }889                890                static crossover(parentA, parentB) {891                    // 두 부모 신경망에서 새 신경망 생성892                    if (parentA.levels.length !== parentB.levels.length) {893                        console.error("부모 신경망 구조가 다릅니다!");894                        return parentA.clone();895                    }896                    897                    const childNetwork = new NeuralNetwork([]);898                    childNetwork.levels = [];899                    900                    for (let l = 0; l < parentA.levels.length; l++) {901                        const levelA = parentA.levels[l];902                        const levelB = parentB.levels[l];903                        904                        if (levelA.inputs.length !== levelB.inputs.length || 905                            levelA.outputs.length !== levelB.outputs.length) {906                            console.error("부모 레벨 구조가 다릅니다!");907                            return parentA.clone();908                        }909                        910                        const childLevel = new Level(levelA.inputs.length, levelA.outputs.length);911                        912                        // 교차점 선택 (단일점 교차)913                        const biasesSwitch = Math.floor(Math.random() * levelA.biases.length);914                        915                        // 바이어스 교차916                        for (let i = 0; i < childLevel.biases.length; i++) {917                            childLevel.biases[i] = i < biasesSwitch 918                                ? levelA.biases[i] 919                                : levelB.biases[i];920                        }921                        922                        // 가중치 교차923                        for (let i = 0; i < childLevel.weights.length; i++) {924                            const weightSwitch = Math.floor(Math.random() * levelA.weights[i].length);925                            926                            for (let j = 0; j < childLevel.weights[i].length; j++) {927                                childLevel.weights[i][j] = j < weightSwitch 928                                    ? levelA.weights[i][j] 929                                    : levelB.weights[i][j];930                            }931                        }932                        933                        childNetwork.levels.push(childLevel);934                    }935                    936                    return childNetwork;937                }938                939                toJSON() {940                    return {941                        levels: this.levels.map(level => ({942                            inputs: level.inputs,943                            outputs: level.outputs,944                            biases: level.biases,945                            weights: level.weights946                        }))947                    };948                }949                950                static fromJSON(data) {951                    const network = new NeuralNetwork([]);952                    network.levels = data.levels.map(levelData => {953                        const level = new Level(levelData.inputs.length, levelData.outputs.length);954                        level.inputs = [...levelData.inputs];955                        level.outputs = [...levelData.outputs];956                        level.biases = [...levelData.biases];957                        level.weights = levelData.weights.map(w => [...w]);958                        return level;959                    });960                    return network;961                }962            }963            964            function lerp(a, b, t) {965                return a + (b - a) * t;966            }967            968            class Level {969                constructor(inputCount, outputCount) {970                    this.inputs = new Array(inputCount);971                    this.outputs = new Array(outputCount);972                    this.biases = new Array(outputCount);973                    this.weights = [];974                    975                    for (let i = 0; i < inputCount; i++) {976                        this.weights[i] = new Array(outputCount);977                    }978                    979                    Level.#randomize(this);980                }981                982                static #randomize(level) {983                    for (let i = 0; i < level.inputs.length; i++) {984                        for (let j = 0; j < level.outputs.length; j++) {985                            level.weights[i][j] = Math.random() * 2 - 1;986                        }987                    }988                    989                    for (let i = 0; i < level.biases.length; i++) {990                        level.biases[i] = Math.random() * 2 - 1;991                    }992                }993                994                static feedForward(givenInputs, level) {995                    // 입력 값 설정996                    for (let i = 0; i < level.inputs.length; i++) {997                        level.inputs[i] = givenInputs[i];998                    }999                    1000                    // 각 출력 뉴런에 대해 가중 합계 계산1001                    for (let i = 0; i < level.outputs.length; i++) {1002                        let sum = 0;1003                        for (let j = 0; j < level.inputs.length; j++) {1004                            sum += level.inputs[j] * level.weights[j][i];1005                        }1006                        1007                        // Sigmoid 활성화 함수 적용1008                        level.outputs[i] = sigmoid(sum - level.biases[i]);1009                    }1010                    1011                    return level.outputs;1012                }1013                1014                clone() {1015                    const clone = new Level(this.inputs.length, this.outputs.length);1016                    clone.inputs = [...this.inputs];1017                    clone.outputs = [...this.outputs];1018                    clone.biases = [...this.biases];1019                    clone.weights = this.weights.map(arr => [...arr]);1020                    return clone;1021                }1022            }1023            1024            // 유전 알고리즘 함수1025            function nextGeneration() {1026                generation++;1027                generationCount.textContent = generation;1028                1029                // 적합도 계산1030                calculateFitness();1031                1032                // 새 인구 생성1033                const newPopulation = [];1034                1035                // 적응형 돌연변이율 적용1036                const progressRate = bestCarProgress;1037                const adaptedRate = mutationRate * (1 - progressRate * 0.5);1038                mutationRate = Math.max(0.01, adaptedRate); // 최소 1%1039                mutationValue.textContent = `${Math.round(mutationRate * 100)}%`;1040                mutationSlider.value = Math.round(mutationRate * 100);1041                1042                // 이전 세대에서 최고 자동차 추가 (엘리티즘)1043                const eliteCount = Math.max(1, Math.floor(populationSize * 0.05)); // 5% 엘리트1044                const eliteCars = getTopCars(eliteCount);1045                1046                for (const eliteCar of eliteCars) {1047                    eliteCar.isBest = eliteCar === eliteCars[0];1048                    newPopulation.push(eliteCar.clone());1049                }1050                1051                // 교차와 돌연변이로 나머지 채우기1052                while (newPopulation.length < populationSize) {1053                    if (Math.random() < 0.7 && newPopulation.length + 1 < populationSize) {1054                        // 교차1055                        const parentA = selectParent();1056                        const parentB = selectParent();1057                        const child = new Car(NeuralNetwork.crossover(parentA.brain, parentB.brain));1058                        1059                        // 자식에게 약간의 돌연변이 적용1060                        child.brain.mutate(mutationRate);1061                        newPopulation.push(child);1062                    } else {1063                        // 돌연변이만1064                        const parent = selectParent();1065                        const child = parent.clone();1066                        child.brain.mutate(mutationRate);1067                        newPopulation.push(child);1068                    }1069                }1070                1071                // 이전 인구 교체1072                cars = newPopulation;1073                1074                // 자동차 초기화1075                cars.forEach(car => car.reset());1076                1077                // 진행률 초기화1078                bestCarProgress = 0;1079                bestProgressBar.style.width = '0%';1080            }1081            1082            function calculateFitness() {1083                let sum = 0;1084                let max = 0;1085                1086                cars.forEach(car => {1087                    // 체크포인트 달성 보너스1088                    car.fitness += car.checkpointIndex * 500;1089                    1090                    sum += car.fitness;1091                    if (car.fitness > max) max = car.fitness;1092                });1093                1094                // 적합도 정규화1095                cars.forEach(car => {1096                    car.fitness = car.fitness / sum;1097                });1098                1099                // UI 업데이트1100                maxFitness.textContent = Math.round(max);1101            }1102            1103            function getTopCars(count) {1104                return [...cars]1105                    .sort((a, b) => b.fitness - a.fitness)1106                    .slice(0, count);1107            }1108            1109            function getBestCar() {1110                let bestCar = cars[0];1111                let bestFitness = cars[0].fitness;1112                1113                for (let i = 1; i < cars.length; i++) {1114                    if (cars[i].fitness > bestFitness) {1115                        bestFitness = cars[i].fitness;1116                        bestCar = cars[i];1117                    }1118                }1119                1120                return bestCar;1121            }1122            1123            function selectParent() {1124                // 룰렛 휠 선택1125                let index = 0;1126                let r = Math.random();1127                1128                while (r > 0 && index < cars.length) {1129                    r -= cars[index].fitness;1130                    index++;1131                }1132                1133                index = Math.min(cars.length - 1, Math.max(0, index - 1));1134                return cars[index];1135            }1136            1137            // 모델 저장/불러오기 함수1138            function saveBestModel() {1139                const bestCar = getBestCar();1140                if (bestCar) {1141                    try {1142                        const modelData = {1143                            brain: bestCar.brain.toJSON(),1144                            fitness: bestCar.fitness,1145                            generation: generation,1146                            timestamp: new Date().toISOString()1147                        };1148                        1149                        localStorage.setItem('bestCarModel', JSON.stringify(modelData));1150                        return true;1151                    } catch (error) {1152                        console.error('Error saving model:', error);1153                        return false;1154                    }1155                }1156                return false;1157            }1158            1159            function loadModel() {1160                try {1161                    const savedModel = localStorage.getItem('bestCarModel');1162                    if (savedModel) {1163                        const modelData = JSON.parse(savedModel);1164                        1165                        // Use saved model for new population1166                        const newPopulation = [];1167                        1168                        // Create best car with restored brain1169                        const restoredBrain = NeuralNetwork.fromJSON(modelData.brain);1170                        const bestCar = new Car(restoredBrain);1171                        bestCar.isBest = true;1172                        newPopulation.push(bestCar);1173                        1174                        // Create variants from this model to fill population1175                        for (let i = 1; i < populationSize; i++) {1176                            const car = bestCar.clone();1177                            car.brain.mutate(mutationRate);1178                            newPopulation.push(car);1179                        }1180                        1181                        // Replace population1182                        cars = newPopulation;1183                        1184                        // Reset cars1185                        cars.forEach(car => car.reset());1186                        1187                        // Create a celebratory confetti effect1188                        createConfetti(50, canvas.width/2, canvas.height/2);1189                        1190                        return true;1191                    }1192                } catch (error) {1193                    console.error('Error loading model:', error);1194                }1195                return false;1196            }1197            1198            // Reduce maximum number of confetti particles1199            const MAX_CONFETTI = 300;1200            const confetti = [];

Showing the first 1,200 of 1477 lines. Download the file for the rest.