Initial commit
implemented the game of life variation with hexagonal grid
This commit is contained in:
183
grid/hex.go
Normal file
183
grid/hex.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package grid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nmoniz/game-of-life/math"
|
||||
)
|
||||
|
||||
type HexLayout struct {
|
||||
Orientation Orientation
|
||||
Origin math.Pair[int]
|
||||
Radius math.Pair[int]
|
||||
}
|
||||
|
||||
func (l HexLayout) HexToPix(h Hex) math.Pair[int] {
|
||||
m := l.Orientation.forward
|
||||
|
||||
x := float64(l.Radius.X) * (m[0]*float64(h.q) + m[1]*float64(h.r))
|
||||
y := float64(l.Radius.Y) * (m[2]*float64(h.q) + m[3]*float64(h.r))
|
||||
|
||||
return math.Pair[int]{
|
||||
X: int(x) + l.Origin.X,
|
||||
Y: int(y) + l.Origin.Y,
|
||||
}
|
||||
}
|
||||
|
||||
func (l HexLayout) PixToHex(c math.Pair[int]) Hex {
|
||||
relC := math.Pair[float64]{
|
||||
X: float64(c.X-l.Origin.X) / float64(l.Radius.X),
|
||||
Y: float64(c.Y-l.Origin.Y) / float64(l.Radius.Y),
|
||||
}
|
||||
|
||||
m := l.Orientation.inverse
|
||||
|
||||
return NewHexRounded(
|
||||
m[0]*relC.X+m[1]*relC.Y,
|
||||
m[2]*relC.X+m[3]*relC.Y,
|
||||
)
|
||||
}
|
||||
|
||||
type Hex struct {
|
||||
q, r, s int
|
||||
}
|
||||
|
||||
func NewHex(q, r int) Hex {
|
||||
return Hex{
|
||||
q: q,
|
||||
r: r,
|
||||
s: -q - r,
|
||||
}
|
||||
}
|
||||
|
||||
func NewHexRounded(q, r float64) Hex {
|
||||
var (
|
||||
s = -q - r
|
||||
qi = math.RoundInt(q)
|
||||
ri = math.RoundInt(r)
|
||||
si = math.RoundInt(s)
|
||||
)
|
||||
|
||||
if qi+ri+si == 0 {
|
||||
return Hex{
|
||||
q: qi,
|
||||
r: ri,
|
||||
s: si,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
qdiff = math.Abs(float64(qi) - q)
|
||||
rdiff = math.Abs(float64(ri) - r)
|
||||
sdiff = math.Abs(float64(si) - s)
|
||||
)
|
||||
|
||||
switch {
|
||||
case qdiff > rdiff && qdiff > sdiff:
|
||||
qi = -ri - si
|
||||
case rdiff > sdiff:
|
||||
ri = -qi - si
|
||||
default:
|
||||
si = -qi - ri
|
||||
}
|
||||
|
||||
return Hex{
|
||||
q: qi,
|
||||
r: ri,
|
||||
s: si,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Hex) Q() int {
|
||||
return h.q
|
||||
}
|
||||
|
||||
func (h Hex) R() int {
|
||||
return h.r
|
||||
}
|
||||
|
||||
func (h Hex) S() int {
|
||||
return h.s
|
||||
}
|
||||
|
||||
func (h Hex) Add(o Hex) Hex {
|
||||
return Hex{
|
||||
q: h.q + o.q,
|
||||
r: h.r + o.r,
|
||||
s: h.s + o.s,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Hex) Sub(o Hex) Hex {
|
||||
return Hex{
|
||||
q: h.q - o.q,
|
||||
r: h.r - o.r,
|
||||
s: h.s - o.s,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Hex) Mul(o Hex) Hex {
|
||||
return Hex{
|
||||
q: h.q * o.q,
|
||||
r: h.r * o.r,
|
||||
s: h.s * o.s,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Hex) Equal(other Hex) bool {
|
||||
return h.q == other.q && h.r == other.r && h.s == other.s
|
||||
}
|
||||
|
||||
func (h Hex) Length() int {
|
||||
return (math.Abs(h.q) + math.Abs(h.r) + math.Abs(h.s)) / 2
|
||||
}
|
||||
|
||||
func (h Hex) Distance(o Hex) int {
|
||||
return h.Sub(o).Length()
|
||||
}
|
||||
|
||||
func (h Hex) AllNeighbors() []Hex {
|
||||
return []Hex{
|
||||
NewHex(h.q+1, h.r),
|
||||
NewHex(h.q, h.r+1),
|
||||
NewHex(h.q-1, h.r+1),
|
||||
NewHex(h.q-1, h.r),
|
||||
NewHex(h.q, h.r-1),
|
||||
NewHex(h.q+1, h.r-1),
|
||||
}
|
||||
}
|
||||
|
||||
func (h Hex) NeighborAt(direction int) Hex {
|
||||
neighbors := h.AllNeighbors()
|
||||
|
||||
direction = direction % 6
|
||||
|
||||
if direction < 0 {
|
||||
direction += 6
|
||||
}
|
||||
|
||||
return neighbors[direction]
|
||||
}
|
||||
|
||||
func (h Hex) String() string {
|
||||
return fmt.Sprintf("(%d,%d,%d)", h.Q(), h.R(), h.S())
|
||||
}
|
||||
|
||||
type Orientation struct {
|
||||
forward []float64
|
||||
inverse []float64
|
||||
}
|
||||
|
||||
func PointyTopLayout() Orientation {
|
||||
return Orientation{
|
||||
forward: []float64{math.Sqrt(3.0), math.Sqrt(3.0) / 2.0, 0.0, 3.0 / 2.0},
|
||||
inverse: []float64{math.Sqrt(3.0) / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0},
|
||||
}
|
||||
}
|
||||
|
||||
func FlatTopLayout() Orientation {
|
||||
return Orientation{
|
||||
forward: []float64{3.0 / 2.0, 0.0, math.Sqrt(3.0) / 2.0, math.Sqrt(3.0)},
|
||||
inverse: []float64{2.0 / 3.0, 0.0, -1.0 / 3.0, math.Sqrt(3.0) / 3.0},
|
||||
}
|
||||
}
|
||||
83
grid/hex_test.go
Normal file
83
grid/hex_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package grid
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHex_NeighborAt(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
hex Hex
|
||||
direction int
|
||||
want Hex
|
||||
}{
|
||||
{
|
||||
name: "zero hex zero direction",
|
||||
hex: Hex{},
|
||||
direction: 0,
|
||||
want: NewHex(1, 0),
|
||||
},
|
||||
{
|
||||
name: "zero hex direction 1",
|
||||
hex: Hex{},
|
||||
direction: 1,
|
||||
want: NewHex(0, 1),
|
||||
},
|
||||
{
|
||||
name: "zero hex direction 2",
|
||||
hex: Hex{},
|
||||
direction: 2,
|
||||
want: NewHex(-1, 1),
|
||||
},
|
||||
{
|
||||
name: "zero hex direction 3",
|
||||
hex: Hex{},
|
||||
direction: 3,
|
||||
want: NewHex(-1, 0),
|
||||
},
|
||||
{
|
||||
name: "zero hex direction 4",
|
||||
hex: Hex{},
|
||||
direction: 4,
|
||||
want: NewHex(0, -1),
|
||||
},
|
||||
{
|
||||
name: "zero hex zero direction 5",
|
||||
hex: Hex{},
|
||||
direction: 5,
|
||||
want: NewHex(1, -1),
|
||||
},
|
||||
{
|
||||
name: "zero hex zero direction overflow",
|
||||
hex: Hex{},
|
||||
direction: 6,
|
||||
want: NewHex(1, 0),
|
||||
},
|
||||
{
|
||||
name: "zero hex zero direction overflow twice",
|
||||
hex: Hex{},
|
||||
direction: 12,
|
||||
want: NewHex(1, 0),
|
||||
},
|
||||
{
|
||||
name: "zero hex zero direction underflow",
|
||||
hex: Hex{},
|
||||
direction: -1,
|
||||
want: NewHex(1, -1),
|
||||
},
|
||||
{
|
||||
name: "zero hex zero direction underflow twice",
|
||||
hex: Hex{},
|
||||
direction: -7,
|
||||
want: NewHex(1, -1),
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tc.hex.NeighborAt(tc.direction)
|
||||
if tc.want != got {
|
||||
t.Fatalf("want %v but got %v", tc.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user