From d7b24d5abc04f2ec4a87a3e2087bee7ea85a940f Mon Sep 17 00:00:00 2001
From: thefish <thefish@zaar.be>
Date: Wed, 13 Nov 2019 02:56:09 +0300
Subject: [PATCH] delaunay/mst working

---
 TODO                               |  23 ++-
 delaunay_test.go                   |  37 +++++
 engine/gamemap/level.go            |  16 +-
 engine/gamemap/mapgens/default.go  |   4 +-
 engine/gamemap/prefab.go           |  12 +-
 engine/types/egde.go               |   6 +
 engine/types/screen.go             |  10 +-
 go.mod                             |   3 +-
 go.sum                             |   6 +-
 ui/keyinput.go                     |   2 +-
 util/{ => appctx}/context.go       |  13 +-
 util/delaunay/delaunay.go          | 256 +++++++++++++++++++++++++++++
 util/delaunay/mst.go               |  61 +++++++
 util/delenay.go                    |   1 -
 util/kruskals/algorithm.go         |  60 +++++++
 util/kruskals/algorithm_test.gp.go |  47 ++++++
 util/uf/uf.go                      |  72 ++++++++
 17 files changed, 589 insertions(+), 40 deletions(-)
 create mode 100644 delaunay_test.go
 create mode 100644 engine/types/egde.go
 rename util/{ => appctx}/context.go (65%)
 create mode 100644 util/delaunay/delaunay.go
 create mode 100644 util/delaunay/mst.go
 delete mode 100644 util/delenay.go
 create mode 100644 util/kruskals/algorithm.go
 create mode 100644 util/kruskals/algorithm_test.gp.go
 create mode 100644 util/uf/uf.go

diff --git a/TODO b/TODO
index 7421375..bba99fe 100644
--- a/TODO
+++ b/TODO
@@ -1,3 +1,15 @@
+Basics:
+    - Items:
+        - place
+        - pickup
+        - drop
+        - use
+    - Targeting Screen
+    - Character Screen
+    - Common letter/string selector for menus to separate struct/file (use it in inventory, title at least)
+    - Move scrollBar ro separate struct/file/function
+
+
 Assets and i18n:
     - move tile settings to json, generate from there (part of prefabs)
     - prepare all interface entries for i18n
@@ -15,13 +27,13 @@ Dungeon and branches:
     - global map of valley
     Prefabs:
     + load prefabs
-    - compose from gens and prefabs
+    + compose from gens and prefabs
     - editor for prefabs
-    Mapgen
+    Mapgen:
     - use delaunay -> minimum spanning tree for room connection (Краскал в gonum)
         github.com/algds/kruskals - MST
         github.com/esimov/triangle - delaunay
-    - или граф относительных окрестностей
+    - или граф относительных окрестностей (?)
 
 Combat:
     - generate skeleton / intesines / muscle / eyes&ears & fingers from templates
@@ -31,11 +43,6 @@ Combat:
     - damage from skill / mass / speed / material density
     - no hitpoints! blood is the life source
 
-Items:
-    - pickup
-    - drop
-    - use
-
 Mobs:
     basic:
     - place mobs
diff --git a/delaunay_test.go b/delaunay_test.go
new file mode 100644
index 0000000..42864bb
--- /dev/null
+++ b/delaunay_test.go
@@ -0,0 +1,37 @@
+package alchemyst_go
+
+import (
+	"lab.zaar.be/thefish/alchemyst-go/engine/types"
+	"lab.zaar.be/thefish/alchemyst-go/util/delaunay"
+	"testing"
+)
+
+func TestDelaunay(t *testing.T) {
+
+	coords := []types.Coords{
+		{10,10},
+		{10,60},
+		{30,10},
+		{40,20},
+		{60,10},
+		{40,60},
+	}
+
+	expected := []types.Edge{
+		{types.Coords{10, 60}, types.Coords{10, 10}},
+		{types.Coords{30, 10}, types.Coords{40, 20}},
+		{types.Coords{60, 10}, types.Coords{40, 60}},
+		{types.Coords{40, 20}, types.Coords{40, 60}},
+		{types.Coords{10, 10,}, types.Coords{30, 10}},
+	}
+
+	result := delaunay.GetMst(coords, 100, 100)
+
+	for idx, _ := range result {
+		if result[idx] != expected[idx] {
+			t.Errorf("Failed, expected %v. got %v", result[idx], expected[idx])
+		}
+
+	}
+	t.Log("output: ", result)
+}
diff --git a/engine/gamemap/level.go b/engine/gamemap/level.go
index c099c73..d932ea5 100644
--- a/engine/gamemap/level.go
+++ b/engine/gamemap/level.go
@@ -3,7 +3,7 @@ package gamemap
 import (
 	"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
 	"lab.zaar.be/thefish/alchemyst-go/engine/types"
-	"lab.zaar.be/thefish/alchemyst-go/util"
+	"lab.zaar.be/thefish/alchemyst-go/util/appctx"
 )
 
 //fixme move to config
@@ -13,12 +13,12 @@ var mapHeight = 90
 
 type Level struct {
 	types.Rect
-	ctx 		util.ClientCtx
-	Name     string
-	Branch   string
-	Depth    int
-	Objects  *[]ecs.Entity
-	Tiles    []*Tile
+	ctx     appctx.ClientCtx
+	Name    string
+	Branch  string
+	Depth   int
+	Objects *[]ecs.Entity
+	Tiles   []*Tile
 }
 
 func (l *Level) GetTile (coords types.Coords) *Tile {
@@ -63,7 +63,7 @@ func (l *Level) Put (x, y int, tileFunc interface{}) {
 	}
 }
 
-func NewLevel(ctx util.ClientCtx, branch string, depth int) *Level {
+func NewLevel(ctx appctx.ClientCtx, branch string, depth int) *Level {
 	l := &Level{
 		ctx: ctx,
 		Name:     branch + string(depth),
diff --git a/engine/gamemap/mapgens/default.go b/engine/gamemap/mapgens/default.go
index 4a05769..4dd901f 100644
--- a/engine/gamemap/mapgens/default.go
+++ b/engine/gamemap/mapgens/default.go
@@ -9,8 +9,8 @@ import (
 
 //fixme move to config
 var minRoomSize = 3
-var maxRoomSize = 22
-var maxrooms = 20
+var maxRoomSize = 15
+var maxrooms = 50
 
 var fges = map[int]types.RectFill{
 	1: types.RectFill{
diff --git a/engine/gamemap/prefab.go b/engine/gamemap/prefab.go
index 8d9141e..8b641bd 100644
--- a/engine/gamemap/prefab.go
+++ b/engine/gamemap/prefab.go
@@ -6,7 +6,7 @@ import (
 	"lab.zaar.be/thefish/alchemyst-go/engine/items"
 	"lab.zaar.be/thefish/alchemyst-go/engine/mob"
 	"lab.zaar.be/thefish/alchemyst-go/engine/types"
-	"lab.zaar.be/thefish/alchemyst-go/util"
+	"lab.zaar.be/thefish/alchemyst-go/util/appctx"
 )
 
 type PrefabFile struct {
@@ -42,10 +42,10 @@ func LoadPrefabFile(filename string) (*PrefabFile, error) {
 }
 
 type PrefabLoader struct {
-	ctx util.ClientCtx
+	ctx appctx.ClientCtx
 }
 
-func NewPrefabLoader(ctx util.ClientCtx) PrefabLoader {
+func NewPrefabLoader(ctx appctx.ClientCtx) PrefabLoader {
 	return PrefabLoader{ctx: ctx}
 }
 
@@ -103,9 +103,9 @@ func (pfbl PrefabLoader) PrefabRoomsList() []Room {
 					room.Connectors = append(room.Connectors, types.Coords{i,j})
 				} else {
 					f, ok = TileTypeMap[shortName]
-				}
-				if (!ok) {
-					pfbl.ctx.Logger().Warn().Msgf("Unknown tile: %s", shortName)
+					if (!ok) {
+						pfbl.ctx.Logger().Warn().Msgf("Unknown tile: %s", shortName)
+					}
 				}
 				room.Geometry[i+ j*room.W] = f
 			}
diff --git a/engine/types/egde.go b/engine/types/egde.go
new file mode 100644
index 0000000..3a29e93
--- /dev/null
+++ b/engine/types/egde.go
@@ -0,0 +1,6 @@
+package types
+
+type Edge struct {
+	From Coords
+	To   Coords
+}
diff --git a/engine/types/screen.go b/engine/types/screen.go
index 8d1dc43..5b0693d 100644
--- a/engine/types/screen.go
+++ b/engine/types/screen.go
@@ -1,7 +1,7 @@
 package types
 
 import (
-	"lab.zaar.be/thefish/alchemyst-go/util"
+	"lab.zaar.be/thefish/alchemyst-go/util/appctx"
 )
 
 type Screen interface {
@@ -13,14 +13,14 @@ type Screen interface {
 }
 
 type ScreenManager struct {
-	ctx util.ClientCtx
-	Screens map[string]Screen
-	CurrentScreen Screen
+	ctx            appctx.ClientCtx
+	Screens        map[string]Screen
+	CurrentScreen  Screen
 	PreviousScreen Screen
 }
 
 // NewScreenManager is a convenience/constructor method to properly initialize a new ScreenManager
-func NewScreenManager(ctx util.ClientCtx) *ScreenManager {
+func NewScreenManager(ctx appctx.ClientCtx) *ScreenManager {
 	manager := ScreenManager{
 		ctx:ctx,
 		Screens: make(map[string]Screen),
diff --git a/go.mod b/go.mod
index fc2333a..3f31c8f 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,9 @@ module lab.zaar.be/thefish/alchemyst-go
 go 1.12
 
 require (
+	github.com/algds/kruskals v0.0.0-20190413211421-a585973d6c2d
+	github.com/algds/uf v0.0.0-20190413195204-b738ae1ba607 // indirect
 	github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622
-	github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
 	github.com/rs/zerolog v1.15.0
 	lab.zaar.be/thefish/bearlibterminal v0.0.0-20191018101635-dd37bbc90d77
 )
diff --git a/go.sum b/go.sum
index 50710e0..bf4bd83 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,10 @@
+github.com/algds/kruskals v0.0.0-20190413211421-a585973d6c2d h1:m2rYLmJHZ3Ovi1CHt/WMpLyzROYGo8mPv8Xyyg/CIvE=
+github.com/algds/kruskals v0.0.0-20190413211421-a585973d6c2d/go.mod h1:hWZ485ODixj1hNmb5Yc1+cArW+TUDXuiN4GAQ2Ly1FA=
+github.com/algds/uf v0.0.0-20190413195204-b738ae1ba607 h1:HUvvlFhycn8608ZhNng9Tzsq4oy7dXnsmg5O4A8awaQ=
+github.com/algds/uf v0.0.0-20190413195204-b738ae1ba607/go.mod h1:tUu0GH4rWbae7BeKBuU/Wbdmp0MkIQwtniqtXBfFgJ0=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622 h1:lxbhOGZ9pU3Kf8P6lFluUcE82yVZn2EqEf4+mWRNPV0=
 github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w=
-github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
-github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
 github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
diff --git a/ui/keyinput.go b/ui/keyinput.go
index 5296710..4f1e6ea 100644
--- a/ui/keyinput.go
+++ b/ui/keyinput.go
@@ -27,7 +27,7 @@ func ReadKey() (string, int) {
 		if blt.Check(blt.TK_CONTROL) != 0 {
 			pressed = "Ctrl+" + pressed
 		}
-		//ctx.Logger().Debug().Msg(pressed)
+		//appctx.Logger().Debug().Msg(pressed)
 	}
 
 	return pressed, key
diff --git a/util/context.go b/util/appctx/context.go
similarity index 65%
rename from util/context.go
rename to util/appctx/context.go
index 96efdf3..3a72699 100644
--- a/util/context.go
+++ b/util/appctx/context.go
@@ -1,9 +1,10 @@
-package util
+package appctx
 
 import (
 	"context"
 	"fmt"
 	"github.com/rs/zerolog"
+	"lab.zaar.be/thefish/alchemyst-go/util"
 )
 
 const (
@@ -15,15 +16,15 @@ type ClientCtx struct {
 	ctx context.Context
 }
 
-func NewClientContext(config *Config, logger *zerolog.Logger) ClientCtx {
+func NewClientContext(config *util.Config, logger *zerolog.Logger) ClientCtx {
 	ctx := context.Context(context.TODO())
 	ctx = context.WithValue(ctx, configKey, config)
 	ctx = context.WithValue(ctx, loggerKey, logger)
 	return ClientCtx{ctx: ctx}
 }
 
-func (c *ClientCtx) Config() *Config {
-	cfg, ok := c.ctx.Value(configKey).(*Config)
+func (c *ClientCtx) Config() *util.Config {
+	cfg, ok := c.ctx.Value(configKey).(*util.Config)
 	if !ok {
 		panic(fmt.Errorf("no access to config from context"))
 	}
@@ -38,7 +39,7 @@ func (c *ClientCtx) Logger() *zerolog.Logger {
 	return logger
 }
 
-//func FromContext(ctx context.Context) (*User, bool) {
-//	u, ok := ctx.Value(userKey).(*User)
+//func FromContext(appctx context.Context) (*User, bool) {
+//	u, ok := appctx.Value(userKey).(*User)
 //	return u, ok
 //}
diff --git a/util/delaunay/delaunay.go b/util/delaunay/delaunay.go
new file mode 100644
index 0000000..58ac466
--- /dev/null
+++ b/util/delaunay/delaunay.go
@@ -0,0 +1,256 @@
+package delaunay
+
+//Original algo courtesy of github.com/esimov/triangle
+
+import "lab.zaar.be/thefish/alchemyst-go/engine/types"
+
+var nodeId = 0
+var nodeList = make(map[types.Coords]Node, 0)
+// Node defines a struct having as components the node X and Y coordinate position.
+type Node struct {
+	Id int
+	types.Coords
+}
+
+// Struct which defines a circle geometry element.
+type circle struct {
+	types.Coords
+	Radius int
+}
+
+// newNode creates a new node.
+func newNode(coords types.Coords) Node {
+	if n, ok := nodeList[coords]; ok {
+		return n
+	}
+	neue := Node{Id: nodeId, Coords: coords}
+	nodeList[coords] = neue
+	nodeId++
+	return neue
+}
+
+// isEq check if two Nodes are approximately equals.
+func (this Node) isEq(other Node) bool {
+	dx := this.X - other.X
+	dy := this.Y - other.Y
+
+	if dx < 0 {
+		dx = -dx
+	}
+	if dy < 0 {
+		dy = -dy
+	}
+	if float64(dx) < 0.0001 && float64(dy) < 0.0001 {
+		return true
+	}
+	return false
+}
+
+// Edge struct having as component the node list.
+type Edge struct {
+	Nodes []Node
+}
+
+// newEdge creates a new Edge.
+func newEdge(this, next Node) []Node {
+	nodes := []Node{this, next}
+	return nodes
+}
+
+// isEq check if two Edge are approximately equals.
+func (e Edge) isEq(edge Edge) bool {
+	na := e.Nodes
+	nb := edge.Nodes
+	na0, na1 := na[0], na[1]
+	nb0, nb1 := nb[0], nb[1]
+
+	if (na0.isEq(nb0) && na1.isEq(nb1)) ||
+		(na0.isEq(nb1) && na1.isEq(nb0)) {
+		return true
+	}
+	return false
+}
+
+// Triangle struct defines the basic components of a triangle.
+// It's constructed by Nodes, it's Edges and the circumcircle which describes the triangle circumference.
+type Triangle struct {
+	Nodes  []Node
+	Edges  []Edge
+	circle circle
+}
+
+var t = Triangle{}
+
+// newTriangle creates a new triangle which circumcircle encloses the point to be added.
+func (t Triangle) newTriangle(p0, p1, p2 Node) Triangle {
+	t.Nodes = []Node{p0, p1, p2}
+	t.Edges = []Edge{{newEdge(p0, p1)}, {newEdge(p1, p2)}, {newEdge(p2, p0)}}
+
+	// Create a circumscribed circle of this triangle.
+	// The circumcircle of a triangle is the circle which has the three vertices of the triangle lying on its circumference.
+	circle := t.circle
+	ax, ay := p1.X-p0.X, p1.Y-p0.Y
+	bx, by := p2.X-p0.X, p2.Y-p0.Y
+
+	m := p1.X*p1.X - p0.X*p0.X + p1.Y*p1.Y - p0.Y*p0.Y
+	u := p2.X*p2.X - p0.X*p0.X + p2.Y*p2.Y - p0.Y*p0.Y
+	s := 1.0 / (2.0 * (float64(ax*by) - float64(ay*bx)))
+
+	circle.Coords.X = int(float64((p2.Y-p0.Y)*m+(p0.Y-p1.Y)*u) * s)
+	circle.Coords.Y = int(float64((p0.X-p2.X)*m+(p1.X-p0.X)*u) * s)
+
+	// Calculate the distance between the node points and the triangle circumcircle.
+	dx := p0.X - circle.Coords.X
+	dy := p0.Y - circle.Coords.Y
+
+	// Calculate the circle radius.
+	circle.Radius = dx*dx + dy*dy
+	t.circle = circle
+
+	return t
+}
+
+// Delaunay defines the main components for the triangulation.
+type Delaunay struct {
+	width     int
+	height    int
+	triangles []Triangle
+}
+
+// Init initialize the delaunay structure.
+func (d *Delaunay) Init(width, height int) *Delaunay {
+	d.width = width
+	d.height = height
+
+	d.triangles = nil
+	d.clear()
+
+	return d
+}
+
+var supertriangle1, supertriangle2 Triangle
+// clear method clears the delaunay triangles slice.
+func (d *Delaunay) clear() {
+	p0 := newNode(types.Coords{0,0})
+	p1 := newNode(types.Coords{d.width, 0})
+	p2 := newNode(types.Coords{d.width, d.height})
+	p3 := newNode(types.Coords{0, d.height})
+
+	// Create the supertriangle, an artificial triangle which encompasses all the points.
+	// At the end of the triangulation process any triangles which share Edges with the supertriangle are deleted from the triangle list.
+	supertriangle1 = t.newTriangle(p0,p1,p2)
+	supertriangle2 = t.newTriangle(p0,p2,p3)
+	d.triangles = []Triangle{supertriangle1, supertriangle2}
+}
+
+// Insert will insert new triangles into the triangles slice.
+func (d *Delaunay) Insert(points []types.Coords) *Delaunay {
+	var (
+		i, j, k      int
+		x, y, dx, dy int
+		distSq       int
+		polygon      []Edge
+		edges        []Edge
+		temps        []Triangle
+	)
+
+	for k = 0; k < len(points); k++ {
+		x = points[k].X
+		y = points[k].Y
+
+		triangles := d.triangles
+		edges = nil
+		temps = nil
+
+		for i = 0; i < len(d.triangles); i++ {
+			t := triangles[i]
+
+			//Check whether the points are inside the triangle circumcircle.
+			circle := t.circle
+			dx = circle.Coords.X - x
+			dy = circle.Coords.Y - y
+			distSq = dx*dx + dy*dy
+
+			if distSq < circle.Radius {
+				// Save triangle Edges in case they are included.
+				edges = append(edges, t.Edges[0], t.Edges[1], t.Edges[2])
+			} else {
+				// If not included carry over.
+				temps = append(temps, t)
+			}
+		}
+
+		polygon = nil
+		// Check duplication of Edges, delete if duplicates.
+	edgesLoop:
+		for i = 0; i < len(edges); i++ {
+			edge := edges[i]
+			for j = 0; j < len(polygon); j++ {
+				// Remove identical Edges.
+				if edge.isEq(polygon[j]) {
+					// Remove polygon from the polygon slice.
+					polygon = append(polygon[:j], polygon[j+1:]...)
+					continue edgesLoop
+				}
+			}
+			// Insert new Edge into the polygon slice.
+			polygon = append(polygon, edge)
+
+		}
+		for i = 0; i < len(polygon); i++ {
+			edge := polygon[i]
+			temps = append(temps, t.newTriangle(edge.Nodes[0], edge.Nodes[1], newNode(types.Coords{x, y})))
+		}
+		d.triangles = temps
+	}
+
+	//Clean up supertriangles
+	z := 0 // output index
+	var valid bool
+	cleanList := make([]Triangle, 0)
+	for _, triangle := range d.triangles {
+		valid = true
+		for _, checkedNode := range triangle.Nodes {
+			for _, superNode := range supertriangle1.Nodes {
+				if checkedNode == superNode {
+					valid = false
+					break
+				}
+			}
+			for _, superNode := range supertriangle2.Nodes {
+				if checkedNode == superNode {
+					valid = false
+					break
+				}
+			}
+			if !valid {
+				break
+			}
+		}
+		if !valid {
+			continue
+		}
+		cleanList = append(cleanList, triangle)
+		z++
+	}
+	d.triangles = cleanList
+
+	//update node indices
+	return d
+}
+
+// GetTriangles return the generated triangles.
+func (d *Delaunay) GetTriangles() []Triangle {
+	return d.triangles
+}
+
+func (d *Delaunay) GetEdges() []Edge {
+	edges := make([]Edge, 0)
+	for _, trs := range d.triangles {
+		for _, e := range trs.Edges {
+			edges = append(edges, e)
+		}
+
+	}
+	return edges
+}
diff --git a/util/delaunay/mst.go b/util/delaunay/mst.go
new file mode 100644
index 0000000..3715d13
--- /dev/null
+++ b/util/delaunay/mst.go
@@ -0,0 +1,61 @@
+package delaunay
+
+import (
+	"lab.zaar.be/thefish/alchemyst-go/engine/types"
+	"lab.zaar.be/thefish/alchemyst-go/util/kruskals"
+)
+//FIXME ПЕРЕПИСАТЬ К ЕБЕНЯМ ДЕЛОНЕ. Это пиздец и си с крестами головного мозга. Надо сделать по-человечески.
+
+func GetTriangles(coords []types.Coords, w, h int) []types.Edge {
+	d := &Delaunay{}
+	edges := d.Init(100, 100).Insert(coords).GetEdges()
+	output := make([]types.Edge, 0)
+	for _, e := range edges{
+		output = append(output, types.Edge{e.Nodes[0].Coords, e.Nodes[1].Coords})
+	}
+	return output
+}
+
+func GetMst(coords []types.Coords, w, h int) []types.Edge {
+
+	d := &Delaunay{}
+
+	edges := d.Init(w, h).Insert(coords).GetEdges()
+
+	//MST
+	//fixme гребаный костыль
+	nodeMap := make(map[int]int, 0)
+	idx := 0
+	nodeList := make(map[int]Node)
+	for _, e := range edges{
+		if _, ok := nodeMap[e.Nodes[0].Id]; !ok{
+			nodeMap[e.Nodes[0].Id] = idx
+			nodeList[idx] = e.Nodes[0]
+			idx++
+		}
+		if _, ok := nodeMap[e.Nodes[1].Id]; !ok{
+			nodeMap[e.Nodes[1].Id] = idx
+			nodeList[idx] = e.Nodes[1]
+			idx++
+		}
+	}
+	//graph := make([]kruskals.WeightedEdge, len(edges))
+	graph := make([]kruskals.WeightedEdge, 0)
+	for _, e := range edges{
+		//graph[i] = kruskals.SimpleWeightedEdge{e.Nodes[0].Id, e.Nodes[1].Id, int(e.Nodes[0].Coords.DistanceTo(e.Nodes[1].Coords))}
+		graph = append(
+			graph,
+			kruskals.SimpleWeightedEdge{
+				nodeMap[e.Nodes[0].Id],
+				nodeMap[e.Nodes[1].Id],
+				int(e.Nodes[0].Coords.DistanceTo(e.Nodes[1].Coords))},
+		)
+	}
+
+	result := kruskals.MinimumSpanningTree(graph)
+	output := make([]types.Edge, 0)
+	for _, we := range result{
+		output = append(output, types.Edge{nodeList[we.From()].Coords, nodeList[we.To()].Coords})
+	}
+	return output
+}
diff --git a/util/delenay.go b/util/delenay.go
deleted file mode 100644
index c7d8682..0000000
--- a/util/delenay.go
+++ /dev/null
@@ -1 +0,0 @@
-package util
diff --git a/util/kruskals/algorithm.go b/util/kruskals/algorithm.go
new file mode 100644
index 0000000..caf3c3a
--- /dev/null
+++ b/util/kruskals/algorithm.go
@@ -0,0 +1,60 @@
+package kruskals
+
+import (
+	"lab.zaar.be/thefish/alchemyst-go/util/uf"
+	"sort"
+
+)
+
+// WeightedEdge is an undirected edge between vertices and a weight.
+type WeightedEdge interface {
+	// From returns the integer identifier of the first vertex.
+	From() int
+	// To returns the integer identifier of the second vertex.
+	To() int
+	// Weight returns the integer identifier of the weight/cost.
+	Weight() int
+}
+
+// SimpleWeightedEdge is a simple implementation of the interface.
+// Initialize with 'F' (From), 'T' (To), 'W' (Weight).
+type SimpleWeightedEdge struct {
+	F, T, W int
+}
+
+// From returns the integer identifier of the first vertex.
+func (s SimpleWeightedEdge) From() int {
+	return s.F
+}
+
+// To returns the integer identifier of the second vertex.
+func (s SimpleWeightedEdge) To() int {
+	return s.T
+}
+
+// Weight returns the integer identifier of the weight/cost.
+func (s SimpleWeightedEdge) Weight() int {
+	return s.W
+}
+
+// MinimumSpanningTree returns the fewest edges to span across the graph
+// with the minimum cost based on weights.
+func MinimumSpanningTree(edges []WeightedEdge) []WeightedEdge {
+	vertices := make(map[int]struct{})
+	for _, e := range edges {
+		vertices[e.From()] = struct{}{}
+		vertices[e.To()] = struct{}{}
+	}
+	sort.Slice(edges, func(i, j int) bool {
+		return edges[i].Weight() < edges[j].Weight()
+	})
+	u := uf.New(len(vertices))
+	result := make([]WeightedEdge, 0, len(edges))
+	for _, e := range edges {
+		if !u.Connected(e.From(), e.To()) {
+			u.Union(e.From(), e.To())
+			result = append(result, e)
+		}
+	}
+	return result
+}
diff --git a/util/kruskals/algorithm_test.gp.go b/util/kruskals/algorithm_test.gp.go
new file mode 100644
index 0000000..813b405
--- /dev/null
+++ b/util/kruskals/algorithm_test.gp.go
@@ -0,0 +1,47 @@
+package kruskals
+
+import (
+"reflect"
+"testing"
+)
+
+var testcases = []struct {
+	graph, mst []WeightedEdge
+}{
+	{
+		[]WeightedEdge{
+			SimpleWeightedEdge{0, 3, 3},
+			SimpleWeightedEdge{3, 1, 30},
+			SimpleWeightedEdge{0, 1, 20},
+			SimpleWeightedEdge{0, 4, 10},
+			SimpleWeightedEdge{1, 4, 5},
+			SimpleWeightedEdge{4, 2, 20},
+			SimpleWeightedEdge{1, 2, 50},
+			SimpleWeightedEdge{3, 0, 3},
+		},
+		[]WeightedEdge{
+			SimpleWeightedEdge{0, 3, 3},
+			SimpleWeightedEdge{1, 4, 5},
+			SimpleWeightedEdge{0, 4, 10},
+			SimpleWeightedEdge{4, 2, 20},
+		},
+	},
+}
+
+func TestMinimumSpanningTree(t *testing.T) {
+	t.Parallel()
+	for _, tc := range testcases {
+		if result := MinimumSpanningTree(tc.graph); !reflect.DeepEqual(result, tc.mst) {
+			t.Errorf("Expected %v, got %v", tc.mst, result)
+		}
+	}
+}
+
+func BenchmarkMinimumSpanningTree(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		for _, tc := range testcases {
+			MinimumSpanningTree(tc.graph)
+		}
+	}
+}
+
diff --git a/util/uf/uf.go b/util/uf/uf.go
new file mode 100644
index 0000000..35ea7b4
--- /dev/null
+++ b/util/uf/uf.go
@@ -0,0 +1,72 @@
+package uf
+
+import "fmt"
+
+// Interface is how you use the union-find data structure.
+type Interface interface {
+	Find(p int) int
+	Count() int
+	Connected(p, q int) bool
+	Union(p, q int)
+	validate(p int)
+}
+
+type instance struct {
+	parent []int
+	rank   []byte
+	count  int
+}
+
+func (i *instance) Find(p int) int {
+	i.validate(p)
+	for p != i.parent[p] {
+		i.parent[p] = i.parent[i.parent[p]]
+		p = i.parent[p]
+	}
+	return p
+}
+
+func (i *instance) Count() int {
+	return i.count
+}
+
+func (i *instance) Connected(p, q int) bool {
+	return i.Find(p) == i.Find(q)
+}
+
+func (i *instance) Union(p, q int) {
+	rootP := i.Find(p)
+	rootQ := i.Find(q)
+	if rootP == rootQ {
+		return
+	}
+	switch {
+	case i.rank[rootP] < i.rank[rootQ]:
+		i.parent[rootP] = rootQ
+	case i.rank[rootP] > i.rank[rootQ]:
+		i.parent[rootQ] = rootP
+	default:
+		i.parent[rootQ] = rootP
+		i.rank[rootP]++
+	}
+	i.count--
+}
+
+func (i *instance) validate(p int) {
+	if n := len(i.parent); p < 0 || p >= n {
+		panic(fmt.Sprintf("index %d is not between 0 and %d", p, n-1))
+	}
+}
+
+// New creates a new instance of a union-find interface.
+func New(n int) Interface {
+	created := &instance{
+		parent: make([]int, n),
+		rank:   make([]byte, n),
+		count:  n,
+	}
+	for i := 0; i < n; i++ {
+		created.parent[i] = i
+	}
+	return created
+}