Skip to content

ColorRpnEvolution.kt

src/jvmMain/kotlin/afield/lang/rpn/ColorRpnEvolution.kt · 87 lines

@file:afield.runtime.completion.CompletionCategory("Search")

package afield.lang.rpn

import afield.document.graph.Box
import afield.study.search.EyeEvolution
import afield.study.search.EyeSetting
import afield.study.search.Offspring
import afield.study.search.evolveByEye

The box-facing front of the ColorRpn family: one call puts a breeding grid of generated pictures in a box's output panel, and clicking two of them breeds the next generation.

colorRpnEvolution is the three other files joined up, with the madp GA demo's choices filled in: ColorRpn as the genome, ColorRpnGenome as the breeding, EyeEvolution.kt's grid as the interface, and a toolbar over the breeding knobs that are worth turning while you watch. Call afield.study.search.evolveByEye directly instead when you want to change any of that.

fun Box.colorRpnEvolution

The madp GA demo in a box: two ColorRpn parents and seven children in a 3×3 grid, bred by ColorRpnGenome; click two tiles and they become the parents of the next generation.

val ga = colorRpnEvolution(
    """
    x 9 * sin 0.18 +
    y 0.55 +
    0.62
    hsl
    """,
    """
    a 14 * cos 0.14 +
    r 0.78 * 0.12 +
    0.48
    hsl
    """,
)

The toolbar holds the demo's mut (mutation rate), len (target length) and res (the resolution each tile renders at), Reroll (new children of the same parents), Fresh Parents (two random programs) and Start over (back to parentA and parentB). The current parents are printed in full under the grid, ready to copy back into the box; re-running the box starts again from the code's parents.

Tiles render on the CPU at resolution² (128, 192, 256 or 320).

@param breeding the breeding rules; ColorRpnBreeding holds the demo's @param seed seeds the breeding, for a reproducible run

fun Box.colorRpnEvolution(
    parentA: String,
    parentB: String,
    breeding: ColorRpnBreeding = ColorRpnBreeding(),
    resolution: Int = 256,
    seed: Long? = null,
    channel: String = "ga",
): EyeEvolution<List<String>> {
    var genome = ColorRpnGenome(breeding)
    var size = resolution
    val settings = listOf(
        EyeSetting("mut", breeding.mutationRate, 0.0, 1.0, step = 0.01, label = "mut") { v ->
            genome = ColorRpnGenome(genome.breeding.copy(mutationRate = v))
        },
        EyeSetting("len", breeding.targetLength.toDouble(), 4.0, 96.0, label = "len") { v ->
            genome = ColorRpnGenome(genome.breeding.copy(targetLength = v.toInt()))
        },
        EyeSetting("res", resolution.toDouble(), 64.0, 512.0, choices = RESOLUTIONS.map { it.toDouble() }, label = "res") { v -> size = v.toInt() },
    )
    return evolveByEye(
        parents = listOf(ColorRpn.tokens(parentA.trimIndent()), ColorRpn.tokens(parentB.trimIndent())),
        breed = { ps, rng -> genome.child(ps[0], ps.getOrElse(1) { ps[0] }, rng).let { Offspring(it.tokens, it.summary) } },
        picture = { tokens -> ColorRpn.compile(tokens.joinToString(" ")).png(size) },
        text = { tokens -> ColorRpn.pretty(tokens) },
        meta = { tokens -> "${ColorRpn.compile(tokens.joinToString(" ")).tokens.size}t" },
        fresh = { rng -> genome.randomProgram(genome.breeding.targetLength, rng) },
        channel = channel,
        title = "ga",
        settings = settings,
        seed = seed,
    )
}

private val RESOLUTIONS = listOf(128, 192, 256, 320)