Skip to content

A golang library for working with hexagonal grids

License

Notifications You must be signed in to change notification settings

legendary-code/hexe

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Go Reference


Logo

Hexe

An easy-to-use golang library for working with hexagonal grids.
Explore the docs Β»

Table of Contents
  1. About The Project
  2. Getting Started
  3. Features
  4. Math Functions
  5. Coordinates
  6. Grid
  7. More Examples
  8. License
  9. Contributing
  10. Contact
  11. Acknowledgements

About The Project

As part of a side-project, I needed a robust library for working with hexagonal grids in Go, but didn't find anything great, so, I decided to implement my own. I stumbled on this great guide that covers just about everything you could want to know about hexagonal grids and algorithms for them. This project is an implementation of that guide as an easy-to-use Go library.

(back to top)

Getting Started

Prerequisites

This library was written with heavy use of generics and some experimental go modules. Your project will require at least Go 1.22.3 installed.

Installation

The library can be installed the usual way with go modules:

go get -u github.com/legendary-code/hexe

(back to top)

Features

These are the features currently supported by this library:

  • Coordinate systems
    • axial
    • cube
    • double-height
    • double-width
    • even-q
    • even-r
    • odd-q
    • odd-r
  • Orientations
    • pointy-top
    • flat-top
  • Cube coordinate math functions
  • Coordinate functions
    • Neighbors
    • Movement Range
    • Set Operations
    • Lines
    • Rings
    • Tracing
    • Field of View
    • Path Finding
  • Grid with load/save functionality

(back to top)

Math Functions

This library provides basic math functions for cubic coordinates, which are then used by the rest of the library. This is a less common use-case, but, is available if needed.

Example:

math_functions.go

package main

import (
	"fmt"
	"github.com/legendary-code/hexe/pkg/hexe/math"
)

func mathFunctionsExample() {
	distance := math.CubeDistance(0, 1, -1, 0, 2, -2)
	fmt.Printf("The distance from (0, 1, -1) to (0, 2, -2) is %d\n", distance)
}

(back to top)

Coordinates

This is the most common usage of this library, working directly with coordinates and sets of coordinates.

Instantiation

instantiation.go

package main

import (
	"fmt"
	"github.com/legendary-code/hexe/pkg/hexe/coord"
)

func instantiationExample() {
	// new axial coordinate (0, 1)
	a := coord.NewAxial(0, 1)

	// convert to cube coordinates (0, 1, -1)
	c := a.Cube()
	fmt.Println(c.Q(), c.R(), c.S())

	// zero value
	c = coord.ZeroCube()

	// accessing components
	fmt.Println(c.Q(), c.R(), c.S())
}

Sets

Some functions return a set of coordinates, which you can easily work with

sets.go

package main

import (
	"fmt"
	"github.com/legendary-code/hexe/pkg/hexe/coord"
)

func setsExample() {
	// Create a set of axial coordinates
	a := coord.NewAxials(
		coord.NewAxial(0, 0),
		coord.NewAxial(0, 1),
		coord.NewAxial(1, 0),
		coord.NewAxial(1, 1),
	)

	// Convert them to cube coordinates
	c := a.Cubes()

	// You can iterate over them
	for iter := c.Iterator(); iter.Next(); {
		fmt.Println(iter.Item())
	}

	// Another way to iterate
	c.ForEach(func(v coord.Cube) bool {
		fmt.Println(v)
		return true
	})
}

(back to top)

Visualization

To help visualize hex grids generated in code, simple plotting functionality is provided for drawing hex grid coordinates and styling the cells.

plot.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func plotExample() {
	fig := plot.NewFigure()

	center := coord.NewAxial(0, 0)
	grid := center.MovementRange(3)

	waterStyle := style.Color(colornames.Lightblue).FontSize(40).Name("🌊")
	landStyle := style.Color(colornames.Sandybrown).FontSize(40).Name("🏝️")

	fig.AddStyledCoords(
		grid,
		waterStyle,
	)

	fig.AddStyledCoords(
		coord.NewAxials(
			coord.NewAxial(0, 0),
			coord.NewAxial(1, 0),
			coord.NewAxial(1, -1),
			coord.NewAxial(0, -1),
			coord.NewAxial(-1, 0),
		),
		landStyle,
	)

	fig.AddStyledCoord(
		coord.NewAxial(1, 1),
		landStyle.Name("πŸ–οΈ"),
	)

	_ = fig.RenderFile("images/plot.svg")
}

Output:

Example

Neighbors

You can calculate neighbors of a coordinate

neighbors.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func neighborsExample() {
	fig := plot.NewFigure()

	center := coord.ZeroAxial()
	neighbors := center.Neighbors()

	fig.AddStyledCoords(neighbors, style.Color(colornames.Lightblue))
	fig.AddCoord(center)

	_ = fig.RenderFile("images/neighbors.svg")
}

Output:

Example

Diagonal Neighbors

This library also supports diagonal neighbors of a coordinate

diagonal_neighbors.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func diagonalNeighborsExample() {
	fig := plot.NewFigure()

	center := coord.ZeroAxial()
	neighbors := center.DiagonalNeighbors()

	fig.AddStyledCoords(neighbors, style.Color(colornames.Lightblue))
	fig.AddCoord(center)

	_ = fig.RenderFile("images/diagonal_neighbors.svg")
}

Output:

Example

Movement Range

Using the movement range on a coord returns all the coordinates that can be reached into a given number of steps

movement_range.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func movementRangeExample() {
	fig := plot.NewFigure()

	center := coord.ZeroAxial()
	movementRange := center.MovementRange(2)

	fig.AddStyledCoords(movementRange, style.Color(colornames.Lightblue))
	fig.AddCoord(center)

	_ = fig.RenderFile("images/movement_range.svg")
}

Output:

Example

Line

Drawing lines is supported as well

line_to.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func lineToExample() {
	fig := plot.NewFigure()

	grid := coord.ZeroAxial().MovementRange(3)
	from := coord.NewAxial(-1, -1)
	to := coord.NewAxial(2, 0)
	line := from.LineTo(to)

	fig.AddCoords(grid)
	fig.AddStyledCoords(line, style.Color(colornames.Lightgreen))

	_ = fig.RenderFile("images/line_to.svg")
}

Output:

Example

Trace

Trace draws a line but with collision detection

trace_to.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func traceToExample() {
	fig := plot.NewFigure()

	grid, walls := createArena()
	from := coord.NewAxial(-1, -1)
	to := coord.NewAxial(0, 2)
	trace := from.TraceTo(to, walls.Contains)

	fig.AddCoords(grid)
	fig.AddStyledCoords(walls, style.Color(colornames.Bisque))
	fig.AddStyledCoords(trace, style.Color(colornames.Lightgreen))

	_ = fig.RenderFile("images/trace_to.svg")
}

Output:

Example

Flood Fill

Flood fill tries to fill an area up to a maximum radius, taking into account blocked areas

flood_fill.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func floodFillExample() {
	fig := plot.NewFigure()

	grid, walls := createArena()
	center := coord.NewAxial(-1, -1)
	fill := center.FloodFill(3, walls.Contains)

	fig.AddCoords(grid)
	fig.AddStyledCoords(walls, style.Color(colornames.Bisque))
	fig.AddStyledCoords(fill, style.Color(colornames.Lightgreen))

	_ = fig.RenderFile("images/flood_fill.svg")
}

Output:

Example

Rotate

You can rotate single coordinates or a set of coordinates around a center in 60-degree increments

rotate.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func rotateExample() {
	fig := plot.NewFigure()

	grid, walls := createArena()
	walls = walls.Rotate(coord.ZeroAxial(), 2)

	fig.AddCoords(grid)
	fig.AddStyledCoords(walls, style.Color(colornames.Bisque))

	_ = fig.RenderFile("images/rotate.svg")
}

Output:

Example

Reflect

You can reflect a coordinate or set of coordinates across the Q, R, or S axis

reflect.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func reflectExample() {
	fig := plot.NewFigure()

	grid, walls := createArena()
	walls = walls.ReflectR()

	fig.AddCoords(grid)
	fig.AddStyledCoords(walls, style.Color(colornames.Bisque))

	_ = fig.RenderFile("images/reflect.svg")
}

Output:

Example

Ring

You can generate rings of various radii

ring.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func ringExample() {
	fig := plot.NewFigure()

	grid, walls := createArena()
	walls = walls.ReflectR()
	ring := coord.ZeroAxial().Ring(1)

	fig.AddCoords(grid)
	fig.AddStyledCoords(walls, style.Color(colornames.Bisque))
	fig.AddStyledCoords(ring, style.Color(colornames.Lightcoral))

	_ = fig.RenderFile("images/ring.svg")
}

Output:

Example

Field Of View

Field of view casts out rays in all directions from a given coordinate to generate the cells visible from the location

field_of_view.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
	"image/color"
)

func fieldOfViewExample() {
	fig := plot.NewFigure()
	grid, walls := createArena()

	person := coord.NewAxial(-1, 2)
	fov := person.FieldOfView(3, walls.Contains)

	wallStyle := style.Color(colornames.Bisque)
	fovStyle := style.Color(color.RGBA{R: 0xdd, G: 0xff, B: 0xdd, A: 0xff})
	personStyle := fovStyle.FontSize(40).Name("🧍")

	fig.AddCoords(grid)
	fig.AddStyledCoords(walls, wallStyle)
	fig.AddStyledCoords(fov, fovStyle)
	fig.AddStyledCoord(person, personStyle)

	_ = fig.RenderFile("images/field_of_view.svg")
}

Output:

Example

Find Path - Breadth First Search

You can perform basic pathfinding with the breadth first search functionality

find_path_bfs.go

package main

import (
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"github.com/legendary-code/hexe/pkg/hexe/plot"
	"github.com/legendary-code/hexe/pkg/hexe/plot/style"
	"golang.org/x/image/colornames"
)

func findPathBfsExample() {
	fig := plot.NewFigure()
	grid, walls := createArena()

	person := coord.NewAxial(-2, 0)
	target := coord.NewAxial(-2, 2)
	path := person.FindPathBFS(target, 20, walls.Contains)

	wallStyle := style.Color(colornames.Bisque)
	pathStyle := style.Color(colornames.Lightblue).FontSize(40)
	personStyle := pathStyle.Name("🧍")
	targetStyle := pathStyle.Name("❌")

	fig.AddCoords(grid)
	fig.AddStyledCoords(walls, wallStyle)
	fig.AddStyledCoords(path, pathStyle)
	fig.AddStyledCoord(person, personStyle)
	fig.AddStyledCoord(target, targetStyle)

	_ = fig.RenderFile("images/find_path_bfs.svg")
}

Output:

Example

(back to top)

Grid

The library also provides a basic Grid[C coords.Coord] collection type for storing and querying values by coordinates.

Grid Operations

grid.go

package main

import (
	"fmt"
	"github.com/legendary-code/hexe/pkg/hexe"
	"github.com/legendary-code/hexe/pkg/hexe/coord"
)

func gridExample() {
	grid := hexe.NewAxialGrid[string]()

	// set some values
	coords := coord.ZeroAxial().MovementRange(2)
	for i := coords.Iterator(); i.Next(); {
		c := i.Item()
		grid.Set(c, fmt.Sprintf("%d", c.Q()+c.R()))
	}

	// remove the center value
	grid.Delete(coord.ZeroAxial())

	// get values for a line
	line := coord.NewAxial(1, 1).LineTo(coord.NewAxial(-1, -1))
	values := grid.GetAll(line)

	// print it out
	for c, value := range values {
		fmt.Printf("%v => %s\n", c, value)
	}
}

Grid Persistence

You can also persist and load grids to any io.Writer/io.Reader

grid_persistence.go

package main

import (
	"fmt"
	"github.com/legendary-code/hexe/pkg/hexe"
	"github.com/legendary-code/hexe/pkg/hexe/coord"
	"strings"
)

type StringEncoderDecoder struct {
}

func (s *StringEncoderDecoder) Encode(value string) ([]byte, error) {
	return []byte(value), nil
}

func (s *StringEncoderDecoder) Decode(bytes []byte) (string, error) {
	return string(bytes), nil
}

func gridPersistenceExample() {
	codec := &StringEncoderDecoder{}
	grid := hexe.NewAxialGrid[string](
		hexe.WithEncoderDecoder[string](codec),
	)

	grid.Set(coord.NewAxial(0, 1), "foo")
	grid.Set(coord.NewAxial(1, 0), "bar")

	sb := strings.Builder{}

	err := grid.Encode(&sb)
	if err != nil {
		panic(err)
	}

	grid.Clear()

	r := strings.NewReader(sb.String())
	err = grid.Decode(r)
	if err != nil {
		panic(err)
	}

	for i := grid.Iterator(); i.Next(); {
		fmt.Printf("%v => %s\n", i.Index(), i.Item())
	}
}

(back to top)

More Examples

For more examples of various features, check out ./examples

(back to top)

License

This library uses the MIT License.

(back to top)

Contributing

Any contributions you make are greatly appreciated.

If you have a suggestion that would make this library better, please fork the repo and create a pull request. You can also file a feature request issue if you don't feel comfortable contributing the solution yourself.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -am 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

(back to top)

Contact

Gene Heinrich
LinkedIn

Acknowledgements

Hexagonal Grids
Best README.md Template

About

A golang library for working with hexagonal grids

Resources

License

Stars

Watchers

Forks

Packages

No packages published