Compare commits

...

2 Commits

Author SHA1 Message Date
105515277f working genetics prototype 2025-12-19 21:41:58 +08:00
afc9904651 we're ready for tenants 2025-12-19 13:48:48 +08:00
9 changed files with 290 additions and 9 deletions

2
go.mod
View File

@@ -1,6 +1,6 @@
module edgaru089.ink/go/gl01 module edgaru089.ink/go/gl01
go 1.17 go 1.25
require ( require (
github.com/aquilax/go-perlin v1.1.0 github.com/aquilax/go-perlin v1.1.0

View File

@@ -9,6 +9,7 @@ import (
"edgaru089.ink/go/gl01/internal/io" "edgaru089.ink/go/gl01/internal/io"
"edgaru089.ink/go/gl01/internal/util" "edgaru089.ink/go/gl01/internal/util"
"edgaru089.ink/go/gl01/internal/util/itype" "edgaru089.ink/go/gl01/internal/util/itype"
"edgaru089.ink/go/gl01/internal/work"
"github.com/go-gl/glfw/v3.3/glfw" "github.com/go-gl/glfw/v3.3/glfw"
) )
@@ -110,9 +111,9 @@ func (g *Game) Init(win *glfw.Window) {
} }
}) })
} work.Init()
const airAccel = 0.1 }
// Update updates the game state, not necessarily in the main thread. // Update updates the game state, not necessarily in the main thread.
func (g *Game) Update(win *glfw.Window, delta time.Duration) { func (g *Game) Update(win *glfw.Window, delta time.Duration) {
@@ -121,6 +122,8 @@ func (g *Game) Update(win *glfw.Window, delta time.Duration) {
clock := util.NewClock() clock := util.NewClock()
work.Update(delta)
io.Diagnostics.Times.Logic = clock.Restart() io.Diagnostics.Times.Logic = clock.Restart()
g.imgui() g.imgui()

View File

@@ -28,4 +28,6 @@ var (
} }
} }
} }
StepFactors []float64
) )

View File

@@ -17,8 +17,9 @@ func NewClock() (c *Clock) {
// Restart resets the start time. // Restart resets the start time.
// It also returns the elapsed time. // It also returns the elapsed time.
func (c *Clock) Restart() (t time.Duration) { func (c *Clock) Restart() (t time.Duration) {
t = time.Since(c.t) now := time.Now()
c.t = time.Now() t = now.Sub(c.t)
c.t = now
return return
} }

9
internal/util/slice.go Normal file
View File

@@ -0,0 +1,9 @@
package util
func Slice2Float32[T int | float64](s []T) (sf []float32) {
sf = make([]float32, len(s))
for i := range s {
sf[i] = float32(s[i])
}
return
}

144
internal/work/work.go Normal file
View File

@@ -0,0 +1,144 @@
package work
import (
"math"
"slices"
"time"
"edgaru089.ink/go/gl01/internal/igwrap"
"edgaru089.ink/go/gl01/internal/io"
"github.com/inkyblackness/imgui-go/v4"
)
var state struct {
inst [2000]*WorkerInstance
buf []*WorkerInstance
steps int
first_step int
iter_disabled bool
}
type iter_info struct {
first_step, last_step int
step_count int
max_score float64
factor float64
}
var stats []iter_info
func Init() {
for i := range state.inst {
inst := RandomInstance()
state.inst[i] = &inst
}
}
func Update(delta time.Duration) {
state.steps++
for i := range state.inst {
state.inst[i].Step(state.steps)
}
slices.SortFunc(state.inst[:], func(x, y *WorkerInstance) int {
if x.score > y.score {
return -1
} else if x.score == y.score && x.pos.Sub(x.target).Len() < y.pos.Sub(y.target).Len() {
return -1
} else {
return 1
}
})
if imgui.Checkbox("Disable Iteration", &state.iter_disabled) {
}
if !state.iter_disabled && (imgui.Button("Iterate") || state.inst[50].score > 0) {
factor := 0.0
for _, f := range io.StepFactors {
factor += f
}
factor /= float64(len(io.StepFactors))
io.StepFactors = io.StepFactors[0:0]
// push stats
stats = append(stats, iter_info{
first_step: state.first_step,
last_step: state.steps,
step_count: state.steps - state.first_step,
max_score: state.inst[0].score,
factor: factor,
})
state.first_step = state.steps
state.buf = state.buf[:0]
for i := range 50 {
state.inst[i].score = 0
state.buf = append(state.buf, state.inst[i])
for range 2000/50 - 1 {
inst := state.inst[i].Offspring()
inst.step0 = state.steps
state.buf = append(state.buf, &inst)
}
}
copy(state.inst[:], state.buf)
}
for i := range 20 {
igwrap.Text("Score: %f\n", state.inst[i].score)
}
if igwrap.Begin("Graph", nil, 0) {
imgui.PushItemWidth(-math.SmallestNonzeroFloat32)
s := make([]float32, len(stats))
if len(stats) < 1 {
goto err
}
for i, st := range stats {
s[i] = float32(st.step_count)
}
if len(s) > 400 {
s = s[len(s)-400:]
}
imgui.PlotLinesV(
"##Step Count",
s,
0,
"",
math.MaxFloat32,
math.MaxFloat32,
imgui.Vec2{0, 200},
)
s = make([]float32, len(stats))
for i, st := range stats {
s[i] = float32(st.factor)
}
if len(s) > 400 {
s = s[len(s)-400:]
}
imgui.PlotLinesV(
"##Factor",
s,
0,
"",
math.MaxFloat32,
math.MaxFloat32,
imgui.Vec2{0, 200},
)
err:
imgui.PopItemWidth()
imgui.End()
}
}

122
internal/work/worker.go Normal file
View File

@@ -0,0 +1,122 @@
package work
import (
"math/rand"
"edgaru089.ink/go/gl01/internal/io"
"github.com/go-gl/mathgl/mgl64"
)
type Worker struct {
t1, t2 mgl64.Mat4
t3 mgl64.Mat2x4
}
func randomSlice64(s []float64) {
for i := range s {
s[i] = rand.NormFloat64()
}
}
// RandomWorker generates a random worker.
func RandomWorker() (w Worker) {
randomSlice64(w.t1[:])
randomSlice64(w.t2[:])
randomSlice64(w.t3[:])
return
}
func mutateSlice64(s []float64, stddev float64) {
for i := range s {
s[i] = rand.NormFloat64()*stddev + s[i]
}
}
// Offspring generates an offspring with the given variance.
func (w *Worker) Offspring(stddev float64) (s Worker) {
s = *w
mutateSlice64(s.t1[:], stddev)
mutateSlice64(s.t2[:], stddev)
mutateSlice64(s.t3[:], stddev)
return
}
// Calculate runs a Vec4 through the Worker.
func (w *Worker) Calculate(input mgl64.Vec4) (output mgl64.Vec2) {
imm1 := w.t1.Mul4x1(input)
imm2 := w.t2.Mul4x1(imm1)
output = w.t3.Mul4x1(imm2)
return
}
// WorkerInstance is a instance of a worker.
type WorkerInstance struct {
worker Worker
pos mgl64.Vec2
target mgl64.Vec2
score float64
distance float64
step0 int
}
var (
Radius float64 = 10 // Radius of all worker instances
Step float64 = 1 // Move length of a advance step
OffspringStdVar float64 = 0.5
Score float64 = 1
TargetAreaSize = mgl64.Vec2{750, 750}
)
func randomTarget() (t mgl64.Vec2) {
return mgl64.Vec2{
TargetAreaSize[0] * (rand.Float64() - .5),
TargetAreaSize[1] * (rand.Float64() - .5),
}
}
func (i *WorkerInstance) Step(step_num int) {
output := i.worker.Calculate(mgl64.Vec4{
i.pos[0],
i.pos[1],
i.target[0],
i.target[1],
})
direction := mgl64.Vec2{output[0], output[1]}.Normalize()
step := direction.Mul(Step)
i.pos = i.pos.Add(step)
if i.pos.Sub(i.target).Len() < Radius {
// Score!
i.score += Score
io.StepFactors = append(io.StepFactors, (float64(step_num-i.step0)*Step)/i.distance)
i.target = randomTarget()
i.step0 = step_num
i.distance = i.pos.Sub(i.target).Len()
}
}
func (i *WorkerInstance) Offspring() (it WorkerInstance) {
it.pos = randomTarget()
it.target = randomTarget()
it.distance = it.pos.Sub(it.target).Len()
it.score = 0
it.worker = i.worker.Offspring(OffspringStdVar)
return
}
func RandomInstance() (i WorkerInstance) {
i.worker = RandomWorker()
i.pos = randomTarget()
i.target = randomTarget()
i.distance = i.pos.Sub(i.target).Len()
i.score = 0
return
}

View File

@@ -2,6 +2,6 @@
"WindowWidth": 1024, "WindowWidth": 1024,
"WindowHeight": 768, "WindowHeight": 768,
"FramerateLimit": 100 "FramerateLimit": 4000
} }

View File

@@ -8,6 +8,7 @@ import (
"edgaru089.ink/go/gl01/internal/game" "edgaru089.ink/go/gl01/internal/game"
gio "edgaru089.ink/go/gl01/internal/io" gio "edgaru089.ink/go/gl01/internal/io"
_ "edgaru089.ink/go/gl01/internal/render/gpu_preference" _ "edgaru089.ink/go/gl01/internal/render/gpu_preference"
"edgaru089.ink/go/gl01/internal/util"
"edgaru089.ink/go/gl01/internal/util/itype" "edgaru089.ink/go/gl01/internal/util/itype"
"github.com/go-gl/gl/all-core/gl" "github.com/go-gl/gl/all-core/gl"
"github.com/go-gl/glfw/v3.3/glfw" "github.com/go-gl/glfw/v3.3/glfw"
@@ -70,15 +71,14 @@ func main() {
gl.ClearColor(gio.ClearColor[0], gio.ClearColor[1], gio.ClearColor[2], gio.ClearColor[3]) gl.ClearColor(gio.ClearColor[0], gio.ClearColor[1], gio.ClearColor[2], gio.ClearColor[3])
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
win.SwapBuffers() win.SwapBuffers()
winClock := time.Now() winClock := util.NewClock()
fpsClock := time.Now() fpsClock := time.Now()
fpsCounter := 0 fpsCounter := 0
for !win.ShouldClose() { for !win.ShouldClose() {
deltaTime := time.Since(winClock) deltaTime := winClock.Restart()
winClock = time.Now()
game.Update(win, deltaTime) game.Update(win, deltaTime)
gl.ClearColor(gio.ClearColor[0], gio.ClearColor[1], gio.ClearColor[2], gio.ClearColor[3]) gl.ClearColor(gio.ClearColor[0], gio.ClearColor[1], gio.ClearColor[2], gio.ClearColor[3])