delaunay/mst working
This commit is contained in:
parent
f55549a048
commit
d7b24d5abc
23
TODO
23
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
|
||||
|
37
delaunay_test.go
Normal file
37
delaunay_test.go
Normal file
@ -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)
|
||||
}
|
@ -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),
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
6
engine/types/egde.go
Normal file
6
engine/types/egde.go
Normal file
@ -0,0 +1,6 @@
|
||||
package types
|
||||
|
||||
type Edge struct {
|
||||
From Coords
|
||||
To Coords
|
||||
}
|
@ -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),
|
||||
|
3
go.mod
3
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
|
||||
)
|
||||
|
6
go.sum
6
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=
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
//}
|
256
util/delaunay/delaunay.go
Normal file
256
util/delaunay/delaunay.go
Normal file
@ -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
|
||||
}
|
61
util/delaunay/mst.go
Normal file
61
util/delaunay/mst.go
Normal file
@ -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
|
||||
}
|
@ -1 +0,0 @@
|
||||
package util
|
60
util/kruskals/algorithm.go
Normal file
60
util/kruskals/algorithm.go
Normal file
@ -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
|
||||
}
|
47
util/kruskals/algorithm_test.gp.go
Normal file
47
util/kruskals/algorithm_test.gp.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
72
util/uf/uf.go
Normal file
72
util/uf/uf.go
Normal file
@ -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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user