goids/main.go

291 lines
6.1 KiB
Go
Raw Normal View History

2022-10-20 07:26:58 +00:00
package main
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"image/color"
"image/png"
"io/ioutil"
2022-10-20 07:26:58 +00:00
"math"
"math/rand"
"os"
"sort"
2022-11-14 21:34:25 +00:00
"strconv"
2022-10-20 07:26:58 +00:00
"github.com/llgcode/draw2d/draw2dimg"
"gopkg.in/yaml.v2"
2022-10-20 07:26:58 +00:00
)
// parameters
2022-11-11 21:57:17 +00:00
var windowWidth, windowHeight = 1000, 700
2022-10-20 07:26:58 +00:00
var goidSize = 3
var goidColor = color.RGBA{200, 200, 100, 255} // gray, 50% transparency
2022-11-16 17:02:35 +00:00
var populationSize = 1
var loops = 2000
2022-11-14 21:34:25 +00:00
var numNeighbours = 5
2022-10-20 07:26:58 +00:00
var separationFactor = float64(goidSize * 5)
var coherenceFactor = 8
type steps struct {
2022-11-14 21:34:25 +00:00
Step int `yaml:"step"`
Amount int `yaml:"amount"`
2022-11-11 21:57:17 +00:00
NumNeighbours int `yaml:"neighbours"`
}
2022-11-14 21:34:25 +00:00
type Coor struct {
X int `yaml:"x"`
Y int `yaml:"y"`
}
type OutPut struct {
Goid int `yaml:"goid"`
Loop int `yaml:"loop"`
Data Coor `yaml:"data"`
}
2022-11-11 21:57:17 +00:00
func fixGoids(gs []*Goid, actual int, wished int) ([]*Goid, int) {
if actual < wished {
g := createRandomGoid()
2022-11-11 21:57:17 +00:00
gs = append(gs, &g)
return gs, actual + 1
} else if wished < actual {
2022-11-14 21:34:25 +00:00
return gs[:len(gs)-1], actual - 1
} else {
2022-11-11 21:57:17 +00:00
return gs, actual
}
}
2022-10-20 07:26:58 +00:00
func main() {
2022-11-14 21:34:25 +00:00
fo, err := os.Create("/tmp/goids.yaml")
2022-10-20 07:26:58 +00:00
if err != nil {
panic(err)
}
2022-11-11 21:57:17 +00:00
conf, err := ioutil.ReadFile("config.yaml")
if err != nil {
panic(err)
}
td := []steps{}
err = yaml.Unmarshal(conf, &td)
2022-10-20 07:26:58 +00:00
defer func() {
if err := fo.Close(); err != nil {
panic(err)
}
}()
clearScreen()
hideCursor()
2022-11-16 17:02:35 +00:00
fmt.Println(td)
var goids []*Goid
2022-10-20 07:26:58 +00:00
2022-11-16 17:02:35 +00:00
// for i := 0; i < populationSize; i++ {
// g := createRandomGoid()
// goids = append(goids, &g)
// }
2022-10-20 07:26:58 +00:00
current_stage := 0
2022-10-20 07:26:58 +00:00
for i := 0; i < loops; i++ {
2022-11-11 21:57:17 +00:00
goids, populationSize = fixGoids(goids, populationSize, td[current_stage].Amount)
2022-11-16 17:02:35 +00:00
if td[current_stage].NumNeighbours >= populationSize {
numNeighbours = populationSize - 1
} else {
numNeighbours = td[current_stage].NumNeighbours
}
if i == td[current_stage+1].Step {
current_stage += 1
}
2022-11-14 21:34:25 +00:00
move(goids, fo, i)
2022-10-20 07:26:58 +00:00
frame := draw(goids)
printImage(frame.SubImage(frame.Rect))
}
showCursor()
2022-11-14 21:34:25 +00:00
out, err := ioutil.ReadFile("/tmp/goids.yaml")
if err != nil {
panic(err)
}
output := []OutPut{}
err = yaml.Unmarshal(out, &output)
var total int = 0
for _, goid := range output {
if total < goid.Goid {
total = goid.Goid
}
}
2022-11-15 08:05:27 +00:00
outfile, err := os.Create("./goids.txt")
if err != nil {
panic(err)
}
2022-11-14 21:34:25 +00:00
for i := 0; i <= total; i++{
var x []string
var y []string
2022-11-16 16:09:16 +00:00
outfile.Write([]byte(fmt.Sprintf("GOID: %d\n", i)))
2022-11-14 21:34:25 +00:00
for z := 0; z <= loops; z++ {
check := false
for _, g := range output {
if g.Goid == i && g.Loop == z{
check = true
x = append(x, strconv.Itoa(g.Data.X))
y = append(y, strconv.Itoa(g.Data.Y))
break
}
}
if !check {
x = append(x, "-")
y = append(y, "-")
}
}
2022-11-15 08:05:27 +00:00
outfile.Write([]byte(fmt.Sprintf("%v\n", x)))
outfile.Write([]byte(fmt.Sprintf("%v\n", y)))
2022-11-14 21:34:25 +00:00
}
2022-10-20 07:26:58 +00:00
}
// Goid represents a drawn goid
type Goid struct {
X int // position
Y int
Vx int // velocity
Vy int
R int // radius
Color color.Color
}
func createRandomGoid() (g Goid) {
g = Goid{
X: rand.Intn(windowWidth),
Y: rand.Intn(windowHeight),
Vx: rand.Intn(goidSize),
Vy: rand.Intn(goidSize),
R: goidSize,
Color: goidColor,
}
return
}
// find the nearest neighbours
func (g *Goid) nearestNeighbours(goids []*Goid) (neighbours []Goid) {
neighbours = make([]Goid, len(goids))
for _, goid := range goids {
neighbours = append(neighbours, *goid)
}
sort.SliceStable(neighbours, func(i, j int) bool {
return g.distance(neighbours[i]) < g.distance(neighbours[j])
})
return
}
// distance between 2 goids
func (g *Goid) distance(n Goid) float64 {
x := g.X - n.X
y := g.Y - n.Y
return math.Sqrt(float64(x*x + y*y))
}
// move the goids with the 3 classic boid rules
2022-11-14 21:34:25 +00:00
func move(goids []*Goid, file *os.File, loop int) {
2022-10-20 07:26:58 +00:00
for i, goid := range goids {
neighbours := goid.nearestNeighbours(goids)
separate(goid, neighbours)
align(goid, neighbours)
cohere(goid, neighbours)
2022-11-14 21:34:25 +00:00
position := fmt.Sprintf("- goid: %d\n loop: %d\n data:\n x: %d\n y: %d\n", i, loop, goid.X, goid.Y)
2022-10-20 07:26:58 +00:00
file.Write([]byte(position))
stayInWindow(goid)
}
}
// if goid goes out of the window frame it comes back on the other side
func stayInWindow(goid *Goid) {
if goid.X < 0 {
goid.X = windowWidth - goid.X
} else if goid.X > windowWidth {
goid.X = windowWidth - goid.X
}
if goid.Y < 0 {
goid.Y = windowHeight - goid.Y
} else if goid.Y > windowHeight {
goid.Y = windowHeight - goid.Y
}
}
// steer to avoid crowding local goids
func separate(g *Goid, neighbours []Goid) {
x, y := 0, 0
for _, n := range neighbours[0:numNeighbours] {
if g.distance(n) < separationFactor {
x += g.X - n.X
y += g.Y - n.Y
}
}
g.Vx = x
g.Vy = y
g.X += x
g.Y += y
}
// steer towards the average heading of local goids
func align(g *Goid, neighbours []Goid) {
x, y := 0, 0
for _, n := range neighbours[0:numNeighbours] {
x += n.Vx
y += n.Vy
}
dx, dy := x/numNeighbours, y/numNeighbours
g.Vx += dx
g.Vy += dy
g.X += dx
g.Y += dy
}
// steer to move toward the average position of local goids
func cohere(g *Goid, neighbours []Goid) {
x, y := 0, 0
for _, n := range neighbours[0:numNeighbours] {
x += n.X
y += n.Y
}
dx, dy := ((x/numNeighbours)-g.X)/coherenceFactor, ((y/numNeighbours)-g.Y)/coherenceFactor
g.Vx += dx
g.Vy += dy
g.X += dx
g.Y += dy
}
// draw the goids
func draw(goids []*Goid) *image.RGBA {
dest := image.NewRGBA(image.Rect(0, 0, windowWidth, windowHeight))
gc := draw2dimg.NewGraphicContext(dest)
for _, goid := range goids {
gc.SetFillColor(goid.Color)
gc.MoveTo(float64(goid.X), float64(goid.Y))
gc.ArcTo(float64(goid.X), float64(goid.Y), float64(goid.R), float64(goid.R), 0, -math.Pi*2)
gc.LineTo(float64(goid.X-goid.Vx), float64(goid.Y-goid.Vy))
gc.Close()
gc.Fill()
}
return dest
}
// ANSI escape sequence codes to perform action on terminal
func hideCursor() {
fmt.Print("\033[?25l")
}
func showCursor() {
fmt.Print("\x1b[?25h\n")
}
func clearScreen() {
fmt.Print("\x1b[2J")
}
// this only works for iTerm!
func printImage(img image.Image) {
var buf bytes.Buffer
png.Encode(&buf, img)
imgBase64Str := base64.StdEncoding.EncodeToString(buf.Bytes())
fmt.Printf("\x1b[2;0H\x1b]1337;File=inline=1:%s\a", imgBase64Str)
}