Skip to content

Commit

Permalink
Init
Browse files Browse the repository at this point in the history
  • Loading branch information
minitauros committed Jul 18, 2022
0 parents commit 770a975
Show file tree
Hide file tree
Showing 14 changed files with 1,508 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
113 changes: 113 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Plane

This is a package that was created during a "hackday" at which we attempted to create a [Battlesnake](https://play.battlesnake.com/) and win a battle against each other. The Battlesnake [documentation](https://docs.battlesnake.com/references/useful-algorithms) recommended to use a [flood fill algorithm](https://en.wikipedia.org/wiki/Flood_fill), and I couldn't find an implementation in Go that I could use.

This package contains methods for working with a surface (to be able to tell which coordinates on a surface are/aren't filled) and for flood filling such surfaces, to determine for example how much unfilled surface exists within a possibly enclosed space, or what the quickest path is - even around obstacles - from A to B.


## Surface

```go
package main

import (
"github.com/minitauros/plane"
)

func main() {
// Create new surface.
surface := plane.NewSurface(5, 5)

// Creates a fill as follows.
// . . . . .
// . . . . .
// . . . . .
// x . . . .
// x x . . .
surface.Fill(
plane.Coord{0, 0},
plane.Coord{1, 0},
plane.Coord{0, 1},
)

// Checking if a coord is filled.
surface.IsFilled(plane.Coord{0, 0}) // True

// Removing/unfilling coords.
surface.Remove(plane.Coord{0, 0})
surface.IsFilled(plane.Coord{0, 0}) // False

// Looping over all filled coords.
for coord := range surface.EachFilled() {
// Do something..
}

// Getting all filled coords at once.
surface.GetFilled() // Coords{{0, 0}, {1, 0}, {0, 1}}

// Count filled.
surface.CountFilled() // 3

// Count unfilled.
surface.CountUnfilled() // 22

// Get the total surface.
surface.TotalSurface() // 25

// Getting the center coord.
surface.GetCenter() // plane.Coord{2, 2}

// Checking if a coord fits.
surface.Fits(plane.Coord{-1, -1}) // False
surface.Fits(plane.Coord{0, 0}) // True

// Clone the surface.
// This is useful when passing it to the flood filler, as the flood
// filler will change the surface's state, and you may want to remember
// the original state.
surface.Clone() // *Surface
}

```

## Flood filler

```go
package main

import (
"github.com/minitauros/plane"
)

func main() {
// Flood filler needs a surface to work with.
surface := plane.NewSurface(5, 5)

// Create new flood filler.
ff := plane.NewFloodFiller(surface)

// Fill the plane, using 0,0 as base and starting the flood at 0,1.
// Note that this does **not** fill `base`.
// That means that the whole surface will be filled after this fill, except the `base` coordinate.
// If you want to fill this, call `surface.Fill()`.
ff.Fill(plane.Coord{0, 0}, plane.Coord{0, 1})

// Return the quickest path from 0,0 to 4,4.
// This will go around obstacles.
ff.CountSteps(plane.Coord{0, 0}, plane.Coord{0, 1}) // 9

// Returns true if 5,5 can be reached, i.e. if there are no obstacles (filled coords) in the way that the flood
// cannot pass through in some way.
ff.CanReach(plane.Coord{0, 0}, plane.Coord{5, 5}) // True

// Returns true if 5,5 can be reached, i.e. if there are no obstacles (filled coords) in the way that the flood
// cannot pass through in some way.
// Forces the flood to start at 0,1 and gives it no other options.
ff.CanReachWhenStartingFillAt(plane.Coord{0, 0}, plane.Coord{5, 5}, plane.Coord{0, 1})
}

```

## Notes

This package was created while working under time pressure, because I had to win the Battlesnake hackathon. I have added tests for some cases, but some are missing. So far code seems to be working.
136 changes: 136 additions & 0 deletions coord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package plane

import (
"math"
"strconv"
"strings"
)

// Coord is a coordinate.
type Coord struct {
X int `json:"x"`
Y int `json:"y"`
}

// Equals returns true if the current coord equals the given other coord.
func (c Coord) Equals(other Coord) bool {
return c.X == other.X && c.Y == other.Y
}

// String satisfies stringer.
func (c Coord) String() string {
return strconv.Itoa(c.X) + "," + strconv.Itoa(c.Y)
}

// ConnectsTo returns true if the current coord connects directly to the given other coordinate.
func (c Coord) ConnectsTo(other Coord) bool {
horizontalDiff := abs(c.X - other.X)
verticalDiff := abs(c.Y - other.Y)
return horizontalDiff <= 1 && verticalDiff <= 1 && !(horizontalDiff == 1 && verticalDiff == 1)
}

// GetCoordInDirection returns the first coordinate in the given direction from the current coordinate.
func (c Coord) GetCoordInDirection(d Direction) Coord {
switch d {
case Top:
return Coord{c.X, c.Y + 1}
case Bot:
return Coord{c.X, c.Y - 1}
case Right:
return Coord{c.X + 1, c.Y}
case Left:
return Coord{c.X - 1, c.Y}
}
return c
}

// GetCoordsTo returns the coords of which the direction can be taken to move towards the given `to` coord.
func (c Coord) GetCoordsTo(to Coord) Coords {
coords := make(Coords, 0, 2)
if to.X > c.X {
coords = append(coords, c.GetCoordInDirection(Right))
} else if to.X < c.X {
coords = append(coords, c.GetCoordInDirection(Left))
}
if to.Y > c.Y {
coords = append(coords, c.GetCoordInDirection(Top))
} else if to.Y < c.Y {
coords = append(coords, c.GetCoordInDirection(Bot))
}
return coords
}

// GetDirectionsTo returns the directions that can be taken to move towards the given `to` coord.
func (c Coord) GetDirectionsTo(to Coord) []Direction {
dirs := make([]Direction, 0, 2)
if to.X > c.X {
dirs = append(dirs, Right)
} else if to.X < c.X {
dirs = append(dirs, Left)
}
if to.Y > c.Y {
dirs = append(dirs, Top)
} else if to.Y < c.Y {
dirs = append(dirs, Bot)
}
return dirs
}

// GetCoordsAround returns all coords around the current coord.
func (c Coord) GetCoordsAround() Coords {
return []Coord{
{c.X + 1, c.Y},
{c.X - 1, c.Y},
{c.X, c.Y + 1},
{c.X, c.Y - 1},
}
}

// Coords is an array of coordinates.
type Coords []Coord

// GetIntersections returns of the given coordinates those that intersect with the current ones.
func (coords Coords) GetIntersections(other []Coord) Coords {
var intersections []Coord
for _, coord1 := range coords {
for _, coord2 := range other {
if coord1.Equals(coord2) {
intersections = append(intersections, coord1)
}
}
}
return intersections
}

// Remove removes the given coords from the current ones.
func (coords *Coords) Remove(coordsToRemove ...Coord) {
if len(*coords) == 0 || len(coordsToRemove) == 0 {
return
}
var newCoords Coords
for _, c := range *coords {
var mustRemove bool
for _, cToRemove := range coordsToRemove {
if c.Equals(cToRemove) {
mustRemove = true
}
}
if !mustRemove {
newCoords = append(newCoords, c)
}
}
*coords = newCoords
}

// String satisfies stringer.
func (coords Coords) String() string {
chunks := make([]string, 0, len(coords))
for _, coord := range coords {
chunks = append(chunks, coord.String())
}
return strings.Join(chunks, " ")
}

func abs(in int) int {
return int(math.Abs(float64(in)))
}
Loading

0 comments on commit 770a975

Please sign in to comment.