Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: r/demo/games/ga #3208

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions examples/gno.land/r/demo/games/ga/ga.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package ga

import (
"math"

"gno.land/p/demo/entropy"
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can make this have a Render function?


// Individual represents a single solution in the population
type Individual struct {
Genome string
Fitness float64
}

// Target is the string we want to evolve
const Target = "HEY"

// MutationRate controls how often mutations occur
const MutationRate = 0.01

// PopulationSize defines the number of individuals in the population
const PopulationSize = 100

// Charset defines the possible characters in the genome
var Charset = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ, !")

func Start(ei *entropy.Instance) (int, string) {
var generation = 0
// Step 1: Initialize Population
population := initializePopulation(PopulationSize, ei)

for generation < 200 {
// Step 2: Calculate Fitness
for i := range population {
population[i].Fitness = calculateFitness(population[i].Genome)
}

// Step 3: Check for Solution
best := findBestIndividual(population)
//fmt.Printf("Generation %d: %s (Fitness: %f)\n", generation, best.Genome, best.Fitness)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leftover?

if best.Genome == Target {
println("Target reached!")
return generation, string(best.Genome)
}

// Step 4: Generate Next Generation
population = generateNextGeneration(population, ei)

generation++
}
return generation, ""
}

// initializePopulation creates a random population of individuals
func initializePopulation(size int, ei *entropy.Instance) []Individual {
population := make([]Individual, size)
for i := 0; i < size; i++ {
population[i] = Individual{
Genome: randomGenome(len(Target), ei), // new random each loop
}
}
return population
}

// randomGenome generates a random string of the same length as the target
func randomGenome(length int, ei *entropy.Instance) string {
genome := make([]rune, length)
for i := 0; i < length; i++ {
index := ei.Value() % uint32(len(Charset))
genome[i] = Charset[index]
}
return string(genome)
}

// calculateFitness computes how close an individual is to the target string
func calculateFitness(genome string) float64 {
matches := 0
for i, char := range genome {
if char == rune(Target[i]) {
matches++
}
}
return float64(matches) / float64(len(Target))
}

// findBestIndividual finds the individual with the highest fitness in the population
func findBestIndividual(population []Individual) Individual {
best := population[0]
for _, individual := range population {
if individual.Fitness > best.Fitness {
best = individual
}
}
return best
}

// generateNextGeneration produces the next generation using selection, crossover, and mutation
func generateNextGeneration(population []Individual, ei *entropy.Instance) []Individual {
//println("---generate next generation")
nextGeneration := make([]Individual, len(population))

for i := 0; i < len(population); i++ {
// Selection: Pick two parents
parent1 := selectIndividual(population, ei)
parent2 := selectIndividual(population, ei)

// Crossover: Create a child
childGenome := crossover(parent1.Genome, parent2.Genome, ei)

// Mutation: Introduce random changes
childGenome = mutate(childGenome, ei)

// Add child to the next generation
nextGeneration[i] = Individual{
Genome: childGenome,
}
}

return nextGeneration
}

// selectIndividual selects an individual from the population based on fitness (roulette wheel selection)
func selectIndividual(population []Individual, ei *entropy.Instance) Individual {
totalFitness := 0.0
for _, individual := range population {
totalFitness += individual.Fitness
}
r := (float64(ei.Value()) / float64(math.MaxUint32)) * totalFitness

cumulative := 0.0
for _, individual := range population {
cumulative += individual.Fitness
if cumulative >= r {
return individual
}
}

return population[len(population)-1]
}

// crossover combines two parent genomes to produce a child genome
func crossover(parent1, parent2 string, ei *entropy.Instance) string {
child := make([]rune, len(parent1))

cutPoint := int(ei.Value()) % len(parent1)

for i := 0; i < len(parent1); i++ {
if i < cutPoint {
child[i] = rune(parent1[i])
} else {
child[i] = rune(parent2[i])
}
}
return string(child)
}

// mutate introduces random changes to a genome based on the mutation rate
func mutate(genome string, ei *entropy.Instance) string {
mutated := []rune(genome)
for i := 0; i < len(mutated); i++ {
rv := ei.Value()
if float64(rv) < MutationRate {
index := rv % uint32(len(Charset))
mutated[i] = Charset[index]
}
}
return string(mutated)
}
16 changes: 16 additions & 0 deletions examples/gno.land/r/demo/games/ga/z0_filetest.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"gno.land/p/demo/entropy"
"gno.land/r/demo/games/ga"
)

func main() {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra newline

r := entropy.New()

gen, target := ga.Start(r)
}

// Output:
// Target reached!
Loading