# Evolution

thirty thousand attempts at one journey, and none of them knows the route. A genome is a list of pushes. Whoever lands nearest is more likely to become a parent, so the route is found in a dozen generations

Run it with `swift run --package-path Examples Example-Simulation-Evolution`.

```swift
import Ollin

/// Thirty thousand attempts at the same journey, none of which knows where it is going.
/// Each one carries a genome (a short list of pushes, played back in order) and flies
/// it. Whoever ends the trial closest to the target is likelier to be a parent of the
/// next generation, and that is the whole mechanism: nobody is told the route, and the
/// route is found anyway, usually inside a dozen generations.
///
/// The first generation sprays in every direction. Watch the walls: the population
/// spends a few generations piling into them before a lucky genome slips through a gap,
/// and once one does, the crowd pours after it.
///
/// Drag to move the target, press `space` for a new set of walls, and `r` to start the
/// whole search over from scratch.
@main
final class Evolution_Example: Sketch {
    var run: Evolution!
    var layout = 0

    @Param(0.001 ... 0.2, icon: "dice") var mutationRate = 0.04
    @Param(2 ... 12, icon: "trophy") var tournament = 4

    let start = Vector2(540, 990)
    let goal = Vector2(540, 110)

    override func setup() {
        background(.black)
        noClear()
        // A population, a genome length, and two points. The speed, the length of a
        // trial, and how hard one gene pushes are all worked out from the distance
        // between those points, so none of them is written down here.
        run = makeEvolution(count: 30_000, genes: 28, from: start, to: goal)
        run.measuresGenerations = true       // for the caption, once a generation
        applyLayout()
    }

    override func draw() {
        // A translucent sheet rather than a wipe, so a flight leaves the trail that
        // shows the route it took. (`background` clears outright, however little alpha
        // its color carries.) The sheet is thin because a whole flight takes over a
        // second: fade any faster and the first half of a route is gone before the
        // second half is drawn, which leaves the picture showing arrivals and no path.
        blendMode(.normal)
        noStroke()
        fill(Color(white: 0.02, alpha: 0.05))
        drawRect(0, 0, width, height)

        run.mutationRate = mutationRate
        run.tournament = Int(tournament)
        if mouseIsPressed { run.target = mouse }

        blendMode(.add)
        stepEvolution(run)
        drawParticles(run)

        blendMode(.normal)
        drawWalls()

        let reached = run.lastGeneration.map { " · \(Int($0.arrived * 100))% reached it" } ?? ""
        drawCaption("Evolution · generation \(run.generation) · 30,000 flights\(reached) · "
            + "drag to move the target, space for new walls, r to start over")
    }

    override func keyPressed() {
        if key == " " { layout = (layout + 1) % 3; applyLayout(); restart() }
        if key == "r" { restart() }
    }

    /// Breeding carries the population's learning, so starting over means a new one.
    private func restart() {
        run = makeEvolution(count: 30_000, genes: 28, from: start, to: run.target,
                        seed: variation &+ layout &+ frameCount)
        run.measuresGenerations = true
        run.obstacles = walls
        background(.black)
    }

    private var walls: [Rectangle] = []

    private func applyLayout() {
        switch layout {
        case 1:      // two staggered slots, so the route has to zigzag
            walls = [Rectangle(x: 0, y: 700, width: 700, height: 34),
                     Rectangle(x: 380, y: 420, width: 700, height: 34)]
        case 2:      // a funnel: the only way through is the middle
            walls = [Rectangle(x: 0, y: 560, width: 430, height: 34),
                     Rectangle(x: 650, y: 560, width: 430, height: 34),
                     Rectangle(x: 430, y: 250, width: 220, height: 34)]
        default:     // one wall with a gap off to the side
            walls = [Rectangle(x: 0, y: 620, width: 640, height: 34),
                     Rectangle(x: 800, y: 620, width: 280, height: 34)]
        }
        run.obstacles = walls
    }

    private func drawWalls() {
        noStroke()
        fill(Color(white: 0.26))
        for w in walls { drawRect(w) }
        // The target, and the ring that counts as reaching it.
        fill(Color(red: 1, green: 0.86, blue: 0.4, alpha: 0.9))
        drawCircle(center: run.target, radius: 7)
        noFill()
        stroke(Color(red: 1, green: 0.86, blue: 0.4, alpha: 0.35))
        strokeWeight(1.5)
        drawCircle(center: run.target, radius: run.targetRadius)
    }
}
```
