Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
KotlinとJavaFXで遺伝的アルゴリズムで、特定の文字列に進化させる
import javafx.animation.AnimationTimer
import javafx.application.Application
import javafx.scene.Group
import javafx.scene.Scene
import javafx.scene.canvas.Canvas
import javafx.scene.canvas.GraphicsContext
import javafx.scene.paint.Color
import javafx.scene.text.Font
import javafx.stage.Stage
fun random(min: Double = 0.0, max: Double = 1.1) = (Math.random() * (max - min + 1)) + min
fun newChar(): Char {
var c = Math.floor(random(64.0, 122.0)).toInt()
c = if (c == 64) 32 else c // @ mark to space.
return c.toChar()
}
fun main(args: Array<String>) {
Application.launch(App::class.java, *args)
}
/**
* JavaFXのCanvasを用意して、AnimationTimerでゲームループを実行します。
* ProcessingやP5.jsと同じように、アプリ起動時に、start()関数が呼ばれ、
* ゲームループ内で、draw(ctx:GraphicsContext)が呼ばれます。
* start関数とdraw関数はユーザーが実装する必要があります。
*/
class App : Application() {
lateinit var canvas: Canvas
lateinit var ctx: GraphicsContext
override fun start(primaryStage: Stage) {
canvas = Canvas(1280.0, 720.0)
ctx = canvas.graphicsContext2D
val root = Group()
root.children.add(canvas)
object : AnimationTimer() {
override fun handle(now: Long) {
draw(ctx)
}
}.start()
with(primaryStage) {
title = "遺伝的アルゴリズムでランダム文字列を'$TARGET'に進化させる"
scene = Scene(root, canvas.width, canvas.height)
show()
}
}
}
//////////////////////////////////////////////
fun <T> randIndex(src: List<T>): Int = Math.floor(Math.random() * src.size).toInt()
class DNA(targetLength: Int = 10, var fitness: Double = 0.0) {
var genes: List<Char> = List(targetLength, { newChar() })
fun calcFitness(target: String) {
fitness = calcScore() / target.length
}
fun calcScore(): Double = genes.foldIndexed(0.0) { i, acc, c -> if (TARGET[i] == c) acc + 1.0 else acc }
fun crossover(partnerDNA: DNA): DNA {
val childDNA = DNA(genes.size)
val midpoint = Math.floor(Math.random() * genes.size)
childDNA.genes = genes.mapIndexed { i, c -> if (i > midpoint) c else partnerDNA.genes[i] }
return childDNA
}
fun mutate(rate: Double): DNA {
genes = genes.map { if (Math.random() < rate) newChar() else it }
return this
}
}
data class Point(val x: Int, val y: Int)
fun xy(x: Int): Point = Point(x % 4, x / 4)
const val POPULATION_COUNT = 150
const val MUTATION_RATE = 0.01
const val TARGET = "to be or not to be"
var foundFlag = false
// ステップ1. 集団の作成
var population: List<DNA> = List(POPULATION_COUNT) { DNA(TARGET.length) }
var generationCount = 0
fun draw(ctx: GraphicsContext) {
ctx.fill = Color(1.0, 1.0, 1.0, 1.0)
ctx.fillRect(0.0, 0.0, ctx.canvas.width, ctx.canvas.height)
if (!foundFlag) {
// ステップ2.選択
// 集団の各要素の適応度を計算
population.forEach { it.calcFitness(TARGET) }
// 交配プールを作成
val matingPool = population.map { dna ->
val n = Math.floor(dna.fitness * 100.0).toInt()
List(n) { dna }
}.flatten()
// ステップ3.生殖(reproduction)
population = population.map {
val dad = matingPool[randIndex(matingPool)]
val mam = matingPool[randIndex(matingPool)]
val child = dad.crossover(mam)
child.mutate(MUTATION_RATE)
}
population.forEach { it.calcFitness(TARGET) }
foundFlag = (population.indexOfFirst { dna -> dna.genes.joinToString("") == TARGET } != -1)
++generationCount
}
// 表示 /////
ctx.font = Font("Monospaced", 14.0)
population.map { it.genes.joinToString("") }
.forEachIndexed { i, s ->
val (x, y) = xy(i)
ctx.fill = if (s == TARGET) Color.RED else Color.BLACK
ctx.fillText(s, 10.0 + (x * 150), 30.0 + (y * 15))
}
ctx.fill = Color.BLACK
val maxDNA = population.maxBy { dna -> dna.fitness } ?: population[0]
ctx.font = Font("Monospaced", 32.0)
ctx.fillText("Best phrase : ${maxDNA.genes.joinToString("")}", 1280.0 / 2, 50.0)
ctx.font = Font("Monospaced", 20.0)
ctx.fillText("total generations: $generationCount", 1280.0 / 2, 50.0 + 20.0 * 1)
// average
//SUM_DNA_LENGTH
val averageFitness = population.map { it.calcScore() }.average()
ctx.fillText("average fitness : ${Math.floor(averageFitness * 100.0) / 100.0}", 1280.0 / 2, 50.0 + 20.0 * 2)
ctx.fillText("mutation rate : $MUTATION_RATE", 1280.0 / 2, 50.0 + 20.0 * 3)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment