571 lines
12 KiB
Go
571 lines
12 KiB
Go
package aoc
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
)
|
|
|
|
type CoordByteMap struct {
|
|
Field map[Coordinate]byte
|
|
|
|
// The Top-Left-most X/Y
|
|
TLX, TLY int
|
|
// The Bottom-Right-most X/Y
|
|
BRX, BRY int
|
|
|
|
// Options for the 'String' method
|
|
StringEmptyIsSpace bool
|
|
StringEmptyByte byte
|
|
|
|
// If 'Repeats' is true, then any coordinate request will be
|
|
// modded by BRX/BRY
|
|
Repeats bool
|
|
}
|
|
|
|
func NewCoordByteMap() CoordByteMap {
|
|
return CoordByteMap{
|
|
Field: make(map[Coordinate]byte),
|
|
TLX: math.MaxInt,
|
|
TLY: math.MaxInt,
|
|
BRX: math.MinInt,
|
|
BRY: math.MinInt,
|
|
StringEmptyByte: ' ',
|
|
}
|
|
}
|
|
|
|
func (m *CoordByteMap) Copy() *CoordByteMap {
|
|
c := CoordByteMap{
|
|
Field: make(map[Coordinate]byte),
|
|
TLX: m.TLX,
|
|
TLY: m.TLY,
|
|
BRX: m.BRX,
|
|
BRY: m.BRY,
|
|
StringEmptyByte: m.StringEmptyByte,
|
|
}
|
|
for i := range m.Field {
|
|
c.Field[i] = m.Field[i]
|
|
}
|
|
return &c
|
|
}
|
|
|
|
func StringSliceToCoordByteMap(input []string) CoordByteMap {
|
|
ret := CoordByteMap{
|
|
Field: make(map[Coordinate]byte),
|
|
}
|
|
for y := range input {
|
|
for x := range input[y] {
|
|
ret.Put(Coordinate{X: x, Y: y}, input[y][x])
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *CoordByteMap) RowCount() int {
|
|
return m.BRY - m.TLY
|
|
}
|
|
|
|
func (m *CoordByteMap) ColCount() int {
|
|
return m.BRX - m.TLX
|
|
}
|
|
|
|
func (m *CoordByteMap) GetRow(y int) []byte {
|
|
if m.Repeats {
|
|
y = (y + m.BRY) % m.BRY
|
|
}
|
|
var resp []byte
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
resp = append(resp, m.Get(Coordinate{X: x, Y: y}))
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func (m *CoordByteMap) GetCol(x int) []byte {
|
|
if m.Repeats {
|
|
x = (x + m.BRX) % m.BRX
|
|
}
|
|
var resp []byte
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
resp = append(resp, m.Get(Coordinate{X: x, Y: y}))
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func (m *CoordByteMap) AddRow(row []byte) {
|
|
y := m.BRY + 1
|
|
for x := 0; x < len(row); x++ {
|
|
m.Put(Coordinate{X: x + m.TLX, Y: y}, row[x])
|
|
}
|
|
}
|
|
|
|
func (m *CoordByteMap) AddCol(col []byte) {
|
|
x := m.BRX + 1
|
|
for y := 0; y < len(col); y++ {
|
|
m.Put(Coordinate{X: x, Y: y + m.TLY}, col[y])
|
|
}
|
|
}
|
|
|
|
func (m *CoordByteMap) GetPos(x, y int) byte {
|
|
if m.Repeats {
|
|
x = (x + m.BRX) % m.BRX
|
|
y = (y + m.BRY) % m.BRY
|
|
}
|
|
return m.Get(Coordinate{X: x, Y: y})
|
|
}
|
|
|
|
func (m *CoordByteMap) Get(pos Coordinate) byte {
|
|
if m.Repeats {
|
|
pos.X = (pos.X + m.BRX) % m.BRX
|
|
pos.Y = (pos.Y + m.BRY) % m.BRY
|
|
}
|
|
if v, ok := m.Field[pos]; ok {
|
|
return v
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (m *CoordByteMap) Opt(pos Coordinate, def byte) byte {
|
|
if m.Repeats {
|
|
pos.X = (pos.X + m.BRX) % m.BRX
|
|
pos.Y = (pos.Y + m.BRY) % m.BRY
|
|
}
|
|
if v, ok := m.Field[pos]; ok {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func (m *CoordByteMap) Put(pos Coordinate, val byte) {
|
|
if pos.X < m.TLX {
|
|
m.TLX = pos.X
|
|
}
|
|
if pos.Y < m.TLY {
|
|
m.TLY = pos.Y
|
|
}
|
|
if pos.X > m.BRX {
|
|
m.BRX = pos.X
|
|
}
|
|
if pos.Y > m.BRY {
|
|
m.BRY = pos.Y
|
|
}
|
|
m.Field[pos] = val
|
|
}
|
|
|
|
func (m *CoordByteMap) Delete(pos Coordinate) {
|
|
if m.Repeats {
|
|
pos.X = (pos.X + m.BRX) % m.BRX
|
|
pos.Y = (pos.Y + m.BRY) % m.BRY
|
|
}
|
|
delete(m.Field, pos)
|
|
}
|
|
|
|
func (m *CoordByteMap) Height() int {
|
|
return m.BRY - m.TLY
|
|
}
|
|
|
|
func (m *CoordByteMap) Width() int {
|
|
return m.BRX - m.TLX
|
|
}
|
|
|
|
func (m *CoordByteMap) ReplaceAll(o, n byte) {
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
c := Coordinate{X: x, Y: y}
|
|
if m.Get(c) == o {
|
|
m.Put(c, n)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *CoordByteMap) ToByteSlices() [][]byte {
|
|
var ret [][]byte
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
var line []byte
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
line = append(line, m.Get(Coordinate{X: x, Y: y}))
|
|
}
|
|
ret = append(ret, line)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// ContainsCoord returns true if the passed coordinate is within the bounds
|
|
// of the map
|
|
func (m *CoordByteMap) ContainsCoord(c Coordinate) bool {
|
|
if m.Repeats {
|
|
c.X = (c.X + m.BRX) % m.BRX
|
|
c.Y = (c.Y + m.BRY) % m.BRY
|
|
}
|
|
return c.X >= m.TLX && c.X <= m.BRX &&
|
|
c.Y >= m.TLY && c.Y <= m.BRY
|
|
}
|
|
|
|
// RowContains searches a row for a byte
|
|
func (m *CoordByteMap) RowContains(row int, b byte) bool {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
if m.Get(Coordinate{X: x, Y: row}) == b {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ColContains searches a column for a byte
|
|
func (m *CoordByteMap) ColContains(col int, b byte) bool {
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
if m.Get(Coordinate{X: col, Y: y}) == b {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// InsertRowAfter grows the map south, moves all rows after 'row' south
|
|
// Then fills the 'row'th row with 'fill'
|
|
func (m *CoordByteMap) InsertRowAfter(row int, fill byte) {
|
|
m.GrowSouth(1, fill)
|
|
for y := m.BRY; y > row; y-- {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
m.Put(Coordinate{X: x, Y: y}, m.Get(Coordinate{X: x, Y: y - 1}))
|
|
}
|
|
}
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
m.Put(Coordinate{X: x, Y: row}, fill)
|
|
}
|
|
}
|
|
|
|
// InsertColAfter grows the map East, moves all cols after 'col' east
|
|
// Then fills the 'col'th column with 'fill'
|
|
func (m *CoordByteMap) InsertColAfter(col int, fill byte) {
|
|
m.GrowEast(1, fill)
|
|
for x := m.BRX; x > col; x-- {
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
m.Put(Coordinate{X: x, Y: y}, m.Get(Coordinate{X: x - 1, Y: y}))
|
|
}
|
|
}
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
m.Put(Coordinate{X: col, Y: y}, fill)
|
|
}
|
|
}
|
|
|
|
// FindFirst searches left to right, top to bottom for the first
|
|
// instance of b.
|
|
func (m *CoordByteMap) FindFirst(b byte) (Coordinate, error) {
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
c := Coordinate{X: x, Y: y}
|
|
if m.Get(c) == b {
|
|
return c, nil
|
|
}
|
|
}
|
|
}
|
|
return Coordinate{}, errors.New("not found")
|
|
}
|
|
|
|
// FindLast searches right to left, bottom to top for the last
|
|
// instance of b
|
|
func (m *CoordByteMap) FindLast(b byte) (Coordinate, error) {
|
|
for y := m.BRY; y >= m.TLY; y-- {
|
|
for x := m.BRX; x >= m.TLX; x-- {
|
|
c := Coordinate{X: x, Y: y}
|
|
if m.Get(c) == b {
|
|
return c, nil
|
|
}
|
|
}
|
|
}
|
|
return Coordinate{}, errors.New("not found")
|
|
}
|
|
|
|
func (m *CoordByteMap) FindAll(b ...byte) []Coordinate {
|
|
var ret []Coordinate
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
c := Coordinate{X: x, Y: y}
|
|
for i := range b {
|
|
if m.Get(c) == b[i] {
|
|
ret = append(ret, c)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *CoordByteMap) FindAllNot(b ...byte) map[Coordinate]byte {
|
|
ret := make(map[Coordinate]byte)
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
c := Coordinate{X: x, Y: y}
|
|
v := m.Get(c)
|
|
var is bool
|
|
for i := range b {
|
|
if v == b[i] {
|
|
is = true
|
|
break
|
|
}
|
|
}
|
|
if !is {
|
|
ret[c] = v
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// FindAdjacentOrth takes a coordinate and a desired byte, and returns
|
|
// all adjacent coordinates that contain that byte
|
|
func (m *CoordByteMap) FindAdjacent(st Coordinate, val byte) []Coordinate {
|
|
return m.AnyContain([]Coordinate{st.North(), st.NE(), st.East(), st.SE(), st.South(), st.SW(), st.West(), st.NW()}, val)
|
|
}
|
|
|
|
// FindAdjacentOrth takes a coordinate and a desired byte, and returns
|
|
// all adjacent coordinates that contain that byte, Orthogonally
|
|
func (m *CoordByteMap) FindAdjacentOrth(st Coordinate, val byte) []Coordinate {
|
|
return m.AnyContain([]Coordinate{st.North(), st.East(), st.South(), st.West()}, val)
|
|
}
|
|
|
|
func (m *CoordByteMap) AnyContain(check []Coordinate, val byte) []Coordinate {
|
|
var ret []Coordinate
|
|
for _, wrk := range check {
|
|
if m.Get(wrk) == val {
|
|
ret = append(ret, wrk)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// FindAnyAdjacentOrth takes a coordinate and a list of desired bytes, and returns
|
|
// all adjacent coordinates that contain those bytes
|
|
func (m *CoordByteMap) FindAnyAdjacent(st Coordinate, val []byte) []Coordinate {
|
|
return m.AnyContainAny([]Coordinate{st.North(), st.NE(), st.East(), st.SE(), st.South(), st.SW(), st.West(), st.NW()}, val)
|
|
}
|
|
|
|
// FindAnyAdjacentOrth takes a coordinate and a desired byte, and returns
|
|
// all adjacent coordinates that contain that byte, Orthogonally
|
|
func (m *CoordByteMap) FindAnyAdjacentOrth(st Coordinate, val []byte) []Coordinate {
|
|
return m.AnyContainAny([]Coordinate{st.North(), st.East(), st.South(), st.West()}, val)
|
|
}
|
|
|
|
func (m *CoordByteMap) AnyContainAny(check []Coordinate, val []byte) []Coordinate {
|
|
var ret []Coordinate
|
|
for _, wrk := range check {
|
|
for _, bt := range val {
|
|
if m.Get(wrk) == bt {
|
|
ret = append(ret, wrk)
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *CoordByteMap) Count(b byte) int {
|
|
var ret int
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
if m.Get(Coordinate{X: x, Y: y}) == b {
|
|
ret++
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *CoordByteMap) GrowNorth(size int, val byte) {
|
|
tlY := m.TLY - 1
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
m.Put(Coordinate{X: x, Y: tlY}, val)
|
|
}
|
|
}
|
|
|
|
func (m *CoordByteMap) GrowEast(size int, val byte) {
|
|
brX := m.BRX + 1
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
m.Put(Coordinate{X: brX, Y: y}, val)
|
|
}
|
|
}
|
|
|
|
func (m *CoordByteMap) GrowSouth(size int, val byte) {
|
|
tlY := m.BRY + 1
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
m.Put(Coordinate{X: x, Y: tlY}, val)
|
|
}
|
|
}
|
|
|
|
func (m *CoordByteMap) GrowWest(size int, val byte) {
|
|
brX := m.TLX - 1
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
m.Put(Coordinate{X: brX, Y: y}, val)
|
|
}
|
|
}
|
|
|
|
// This grows the map in all directions
|
|
func (m *CoordByteMap) Grow(size int, val byte) {
|
|
m.GrowNorth(size, val)
|
|
m.GrowEast(size, val)
|
|
m.GrowSouth(size, val)
|
|
m.GrowWest(size, val)
|
|
}
|
|
|
|
// Runs the passed function on every point in the map
|
|
// returning a new bytemap
|
|
func (m CoordByteMap) Map(l func(c Coordinate, in byte) byte) CoordByteMap {
|
|
i := m
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
c := Coordinate{X: x, Y: y}
|
|
nV := l(c, m.Get(c))
|
|
fmt.Println("MAP: Putting", string(nV), "at", c)
|
|
i.Put(c, l(c, m.Get(c)))
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
|
|
func (m CoordByteMap) RowAsString(y int) string {
|
|
var res string
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
res = fmt.Sprintf("%s%s", res, string(m.Get(Coordinate{X: x, Y: y})))
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (m CoordByteMap) ColAsString(x int) string {
|
|
var res string
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
res = fmt.Sprintf("%s%s", res, string(m.Get(Coordinate{X: x, Y: y})))
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (m CoordByteMap) String() string {
|
|
var ret string
|
|
for y := m.TLY; y <= m.BRY; y++ {
|
|
for x := m.TLX; x <= m.BRX; x++ {
|
|
if m.StringEmptyIsSpace {
|
|
ret = ret + string(m.Opt(Coordinate{X: x, Y: y}, m.StringEmptyByte))
|
|
} else {
|
|
ret = ret + string(m.Field[Coordinate{X: x, Y: y}])
|
|
}
|
|
}
|
|
ret = ret + "\n"
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// And the 3d Version
|
|
type Coord3dByteMap struct {
|
|
Field map[Coordinate3d]byte
|
|
MinX, MaxX int
|
|
MinY, MaxY int
|
|
MinZ, MaxZ int
|
|
}
|
|
|
|
func StringSliceToCoord3dByteMap(input []string) Coord3dByteMap {
|
|
ret := Coord3dByteMap{
|
|
Field: make(map[Coordinate3d]byte),
|
|
}
|
|
for y := range input {
|
|
for x := range input[y] {
|
|
ret.Put(Coordinate3d{X: x, Y: y, Z: 0}, input[y][x])
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *Coord3dByteMap) Copy() Coord3dByteMap {
|
|
ret := Coord3dByteMap{
|
|
Field: make(map[Coordinate3d]byte),
|
|
MinX: m.MinX, MaxX: m.MaxX,
|
|
MinY: m.MinY, MaxY: m.MaxY,
|
|
MinZ: m.MinZ, MaxZ: m.MaxZ,
|
|
}
|
|
for z := m.MinZ; z <= m.MaxZ; z++ {
|
|
for y := m.MinY; y <= m.MaxY; y++ {
|
|
for x := m.MinX; x <= m.MaxX; x++ {
|
|
c := Coordinate3d{X: x, Y: y, Z: z}
|
|
ret.Field[c] = m.Get(c)
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m *Coord3dByteMap) Get(pos Coordinate3d) byte {
|
|
if _, ok := m.Field[pos]; !ok {
|
|
return 0
|
|
}
|
|
return m.Field[pos]
|
|
}
|
|
|
|
func (m *Coord3dByteMap) Opt(pos Coordinate3d, def byte) byte {
|
|
if v, ok := m.Field[pos]; ok {
|
|
return v
|
|
}
|
|
return def
|
|
}
|
|
|
|
func (m *Coord3dByteMap) Put(pos Coordinate3d, val byte) {
|
|
m.Field[pos] = val
|
|
if pos.X < m.MinX {
|
|
m.MinX = pos.X
|
|
}
|
|
if pos.X > m.MaxX {
|
|
m.MaxX = pos.X
|
|
}
|
|
if pos.Y < m.MinY {
|
|
m.MinY = pos.X
|
|
}
|
|
if pos.Y > m.MaxY {
|
|
m.MaxY = pos.Y
|
|
}
|
|
if pos.Z < m.MinZ {
|
|
m.MinZ = pos.Z
|
|
}
|
|
if pos.Z > m.MaxZ {
|
|
m.MaxZ = pos.Z
|
|
}
|
|
}
|
|
|
|
func (m *Coord3dByteMap) CountNeighbors(pos Coordinate3d, val byte) int {
|
|
var res int
|
|
for z := pos.Z - 1; z <= pos.Z+1; z++ {
|
|
for y := pos.Y - 1; y <= pos.Y+1; y++ {
|
|
for x := pos.X - 1; x <= pos.X+1; x++ {
|
|
if m.Get(Coordinate3d{X: x, Y: y, Z: z}) == val {
|
|
res++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (m Coord3dByteMap) Count(val byte) int {
|
|
var ret int
|
|
for z := m.MinZ; z <= m.MaxZ; z++ {
|
|
for y := m.MinY; y <= m.MaxY; y++ {
|
|
for x := m.MinX; x <= m.MaxX; x++ {
|
|
if m.Get(Coordinate3d{X: x, Y: y, Z: z}) == val {
|
|
ret++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (m Coord3dByteMap) String() string {
|
|
var ret string
|
|
for z := m.MinZ; z <= m.MaxZ; z++ {
|
|
for y := m.MinY; y <= m.MaxY; y++ {
|
|
for x := m.MinX; x <= m.MaxX; x++ {
|
|
ret = ret + string(m.Field[Coordinate3d{X: x, Y: y, Z: z}])
|
|
}
|
|
ret = ret + "\n"
|
|
}
|
|
ret = ret + "\n\n"
|
|
}
|
|
return ret
|
|
}
|