diff --git a/days/18.go b/days/18.go new file mode 100644 index 0000000..4f09032 --- /dev/null +++ b/days/18.go @@ -0,0 +1,349 @@ +package days + +import ( + "fmt" + "math" + "sort" + "strings" + + "github.com/beefsack/go-astar" + u "parnic.com/aoc2019/utilities" +) + +type day18Cell int +type day18Vec u.Vec2[int] + +const ( + day18CellWall day18Cell = iota + day18CellOpen +) + +var ( + day18AdjacentOffsets = []day18Vec{ + {X: -1, Y: 0}, + {X: 1, Y: 0}, + {X: 0, Y: -1}, + {X: 0, Y: 1}, + } +) + +// variables needed for pathfinding +// a better solution would be to keep the current path search state in its +// own struct and store these on there, but i don't need to worry about +// thread safety right now, so this will do fine +var ( + pathGrid [][]day18Cell + pathDoors map[day18Vec]int +) + +type day18KeySearchResult struct { + keyType int + path []astar.Pather + distance float64 + found bool +} + +type Day18 struct { + entrance day18Vec + grid [][]day18Cell + doors map[day18Vec]int + keys map[day18Vec]int + knownPaths map[u.Pair[day18Vec, string]]u.Pair[int, bool] +} + +func (v day18Vec) PathNeighbors() []astar.Pather { + ret := make([]astar.Pather, 0) + + for _, offset := range day18AdjacentOffsets { + vOffset := day18Vec{X: v.X + offset.X, Y: v.Y + offset.Y} + if vOffset.X < 0 || vOffset.Y < 0 || vOffset.Y >= len(pathGrid) || vOffset.X >= len(pathGrid[0]) { + continue + } + + if _, exists := pathDoors[vOffset]; exists { + continue + } + + if pathGrid[vOffset.Y][vOffset.X] == day18CellOpen { + ret = append(ret, vOffset) + } + } + + return ret +} + +func (v day18Vec) PathNeighborCost(to astar.Pather) float64 { + return 1 +} + +func (v day18Vec) PathEstimatedCost(to astar.Pather) float64 { + // this is insanely more complicated than i feel like it should be. + // i guess generics aren't quite as flexible as i'd hoped. Go doesn't + // seem to know that a day18Vec is a vec2i which is a vec2[int]. + return float64(u.ManhattanDistance(u.Vec2[int]{X: v.X, Y: v.Y}, u.Vec2[int]{X: to.(day18Vec).X, Y: to.(day18Vec).Y})) +} + +func (d *Day18) Parse() { + d.doors = make(map[day18Vec]int) + d.keys = make(map[day18Vec]int) + d.knownPaths = make(map[u.Pair[day18Vec, string]]u.Pair[int, bool]) + + lines := u.GetStringLines("18s6") + d.grid = make([][]day18Cell, len(lines)) + for i, line := range lines { + d.grid[i] = make([]day18Cell, len(line)) + for j, char := range line { + if char == '#' { + d.grid[i][j] = day18CellWall + } else if char == '.' { + d.grid[i][j] = day18CellOpen + } else if char == '@' { + d.grid[i][j] = day18CellOpen + d.entrance = day18Vec{X: j, Y: i} + } else if char >= 'A' && char <= 'Z' { + d.grid[i][j] = day18CellOpen + d.doors[day18Vec{X: j, Y: i}] = int(char - 'A') + } else if char >= 'a' && char <= 'z' { + d.grid[i][j] = day18CellOpen + d.keys[day18Vec{X: j, Y: i}] = int(char - 'a') + } + } + } + + pathGrid = d.grid +} + +func (d Day18) Num() int { + return 18 +} + +func (d Day18) Draw(grid [][]day18Cell, keys, doors map[day18Vec]int, entrances ...day18Vec) { + for y := range grid { + for x := range grid[y] { + switch grid[y][x] { + case day18CellWall: + fmt.Print("█") + case day18CellOpen: + posVec := day18Vec{X: x, Y: y} + if _, exists := doors[posVec]; exists { + fmt.Printf("%c", rune(doors[posVec]+'A')) + } else if _, exists := keys[posVec]; exists { + fmt.Printf("%c", rune(keys[posVec]+'a')) + } else if u.ArrayContains(entrances, posVec) { + fmt.Print("@") + } else { + fmt.Print(".") + } + } + } + fmt.Println() + } +} + +func (d *Day18) pickUpKey(atPosition day18Vec, keys, doors map[day18Vec]int) { + key, exists := keys[atPosition] + if !exists { + panic("tried to pick up a key that either doesn't exist or was already picked up") + } + + var matchingDoorPos day18Vec + foundDoor := false + for doorPos, doorType := range doors { + if doorType == key { + matchingDoorPos = doorPos + foundDoor = true + break + } + } + + delete(keys, atPosition) + // the last key has no door, so we should only fail this once. should. + if foundDoor { + delete(doors, matchingDoorPos) + } +} + +func astarPathContains(arr []astar.Pather, find astar.Pather) bool { + for _, v := range arr { + if v == find { + return true + } + } + + return false +} + +func (d Day18) findReachableKeys(pos day18Vec, keys, doors map[day18Vec]int) map[day18Vec]day18KeySearchResult { + pathDoors = doors + + ret := make(map[day18Vec]day18KeySearchResult) + for keyPos, keyType := range keys { + path, distance, found := astar.Path(pos, keyPos) + if found { + ret[keyPos] = day18KeySearchResult{ + keyType: keyType, + path: path, + distance: distance, + found: found, + } + } + } + + if len(ret) < 2 { + return ret + } + + filtered := make(map[day18Vec]day18KeySearchResult) + // filter out keys that require you to walk through another key to get to them + for keyPos, key := range ret { + eligible := true + for checkKeyPos := range ret { + if keyPos == checkKeyPos { + continue + } + + if astarPathContains(key.path, checkKeyPos) { + eligible = false + break + } + } + + if eligible { + filtered[keyPos] = key + } + } + + return filtered +} + +func (d Day18) tryPickupKey(inKeys, inDoors map[day18Vec]int, keyPos day18Vec, keyResult day18KeySearchResult) (int, bool) { + keys := u.CopyMap(inKeys) + doors := u.CopyMap(inDoors) + + d.pickUpKey(keyPos, keys, doors) + totalDist := int(keyResult.distance) + + if len(keys) == 0 { + return totalDist, true + } + + sb := strings.Builder{} + keyVals := u.MapValues(keys) + sort.Slice(keyVals, func(i, j int) bool { return keyVals[i] < keyVals[j] }) + for i, v := range keyVals { + if i > 0 { + sb.WriteRune(',') + } + sb.WriteString(fmt.Sprintf("%d", v)) + } + vecToKey := u.Pair[day18Vec, string]{First: keyPos, Second: sb.String()} + if v, exists := d.knownPaths[vecToKey]; exists { + return totalDist + v.First, v.Second + } + + shortestDist := math.MaxInt + reachableKeys := d.findReachableKeys(keyPos, keys, doors) + if len(reachableKeys) == 0 { + // panic("no reachable keys") + return math.MaxInt, false + } + // fmt.Println("in.", len(reachableKeys), "reachable keys") + done := false + for reachableKeyPos, keyResult := range reachableKeys { + bestDist, finished := d.tryPickupKey(keys, doors, reachableKeyPos, keyResult) + if bestDist < shortestDist { + shortestDist = bestDist + done = finished + } + } + // fmt.Println("out") + d.knownPaths[vecToKey] = u.Pair[int, bool]{First: shortestDist, Second: done} + + totalDist += shortestDist + + return totalDist, done +} + +func (d Day18) part2PatchMap(grid [][]day18Cell, entrance day18Vec) []day18Vec { + grid[entrance.Y-1][entrance.X] = day18CellWall + grid[entrance.Y][entrance.X-1] = day18CellWall + grid[entrance.Y][entrance.X] = day18CellWall + grid[entrance.Y][entrance.X+1] = day18CellWall + grid[entrance.Y+1][entrance.X] = day18CellWall + + return []day18Vec{ + {X: entrance.X - 1, Y: entrance.Y - 1}, + {X: entrance.X + 1, Y: entrance.Y - 1}, + {X: entrance.X - 1, Y: entrance.Y + 1}, + {X: entrance.X + 1, Y: entrance.Y + 1}, + } +} + +func (d *Day18) Part1() string { + pos := d.entrance + keys := u.CopyMap(d.keys) + doors := u.CopyMap(d.doors) + + fmt.Println("initial state:") + d.Draw(d.grid, keys, doors, pos) + + bestDist := math.MaxInt + + reachableKeys := d.findReachableKeys(pos, keys, doors) + if len(reachableKeys) == 0 { + panic("failed to find a key to pick up") + } + for keyPos, keyResult := range reachableKeys { + shortestPath, _ := d.tryPickupKey(keys, doors, keyPos, keyResult) + if shortestPath < bestDist { + bestDist = shortestPath + } + } + + // fmt.Printf("Moving to pick up key %c (cost: %d, total cost so far: %d)\n", rune(closestKey+'a'), closestDist, totalDist) + // d.Draw(pos, keys, doors) + + return fmt.Sprintf("Total distance traveled: %s%d%s", u.TextBold, bestDist, u.TextReset) +} + +func (d *Day18) Part2() string { + keys := u.CopyMap(d.keys) + doors := u.CopyMap(d.doors) + d.knownPaths = make(map[u.Pair[day18Vec, string]]u.Pair[int, bool]) + + fmt.Println("initial state:") + grid := make([][]day18Cell, len(d.grid)) + for i := range d.grid { + grid[i] = make([]day18Cell, len(d.grid[i])) + copy(grid[i], d.grid[i]) + } + pathGrid = grid + + entrances := d.part2PatchMap(grid, d.entrance) + d.Draw(grid, keys, doors, entrances...) + + bestDist := math.MaxInt + + // reachableKeys := d.findReachableKeys(entrances[0], keys, doors) + // if len(reachableKeys) == 0 { + // panic("failed to find a key to pick up") + // } + done := true + for !done { + for _, entrance := range entrances { + reachableKeys := d.findReachableKeys(entrance, keys, doors) + for keyPos, keyResult := range reachableKeys { + shortestPath, finished := d.tryPickupKey(keys, doors, keyPos, keyResult) + if shortestPath < bestDist { + bestDist = shortestPath + done = finished + } + } + } + } + + // fmt.Printf("Moving to pick up key %c (cost: %d, total cost so far: %d)\n", rune(closestKey+'a'), closestDist, totalDist) + // d.Draw(pos, keys, doors) + + return fmt.Sprintf("Total distance traveled: %s%d%s", u.TextBold, bestDist, u.TextReset) +} diff --git a/go.mod b/go.mod index b715a41..08dd14c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module parnic.com/aoc2019 go 1.18 + +require github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7ff33bc --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482 h1:p4g4uok3+r6Tg6fxXEQUAcMAX/WdK6WhkQW9s0jaT7k= +github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482/go.mod h1:Cu3t5VeqE8kXjUBeNXWQprfuaP5UCIc5ggGjgMx9KFc= diff --git a/inputs/18p.txt b/inputs/18p.txt new file mode 100644 index 0000000..9ac4ed0 --- /dev/null +++ b/inputs/18p.txt @@ -0,0 +1,81 @@ +################################################################################# +#...#.........P...................#.....#.........#.......#.........#...........# +#.###.#############.#.###########.#.#.###.#########.###.###.#######.#.########### +#.#...#.#...V.....#.#..t..#.......#.#..a#...........#.#..c..#...#.#.#...........# +#.#.###.#.#######.#####.#.#####.#######.#.###########.#######.#.#.#.###########.# +#...#...#...#.#...#.J.#.#.....#.........#.......#.......#.....#.#.#.......#...#.# +#Z###.#####.#.#.###.#.###.###.#################.#.###.#.#.#####.#.#######.#.#.#.# +#.....#...#.#.......#...#...#.#...#.....#...#.#.#.#...#.#...#.....#.......#.#.#.# +###.###.#.#.###########.###.#.#.#.#.###.#.#.#.#.#.#.###.###.#######.#######.#.#.# +#.#.#...#..y#.........#...#.#...#...#...#.#...#.#.#.#.......#.......#.......#...# +#.#.#.#######.#######.###.#######.###.###.###.#.###.#######.#.#######.#########.# +#.#.#..q........#.....#.#...#.E.#...#...#.#...#...#.....#.#.#.#...........#...#.# +#.#.#############.#####.###.#.#.#######.#.###.###.#.###.#.#.#.#####.#####.#.#.#.# +#.#...F.......#...#...#.......#.........#...#.#...#.#...#.#.#.....#.#.....#.#...# +#.#########.###.###.#S###################.#I###.#####.###.#.#####.#.#.#########.# +#...#...#...#...#...#....u......#.#.....#.#...#.#...#.#...#.....#.#.#.#.......#.# +#.###.#.#.###.###.#############.#.#.###.#####.#.#.#.#.#.#######.#.#.#.#.#####.#.# +#.#...#.#.#.#.#s..#...#hG.#...#.#.#...#.#.....#...#...#.#.......#.#.#...#...#.#.# +#.#.###.#.#.#O#.###.#.#.#.#.#.#.#.#.###.#.###.#########.#.#######.###.###.###.#.# +#m..#.#...#.#.#.#...#...#...#g#.#.#.#...#.#.#.#.........#.#.#...#...#.....#...#.# +#.###.#####.#.#.#H###########.#.#.#.#.###.#.#.###.#.#####.#.#.#.#.#.#.#####.###.# +#.#.........#...#.....#...#...#.#...#...#.#...#...#.#...#.#.#.#.#.#.#.#..w#.#.#.# +#.#.#####.#.#############.#.###U###.###.#.#.###.###.#.#.#.#.###.###.#.#.#.#.#.#.# +#.#.....#.#.#.....#.......#.#.....#.#...#.#.....#...#.#.#.#...#...#.#.#.#...#...# +#.#######.#.###.#.#####.###.#####.###.#.#.#######.###.#.#.#.#####.#.###.#####.### +#.#.......#.....#.....#...#.#...#b#...#.#.#.#.....#...#.....#.....#...#.#.....#.# +#.#.#################.#.#.#.#.#.#.#.###.#.#.#.#####.#########.#######.#.#.#####.# +#.#.#.......#.......#.#.#.#.B.#...#.#.#.#.#.....#...#.....#...#.......#.#...#...# +#.#.#.#######.###.###.#.#.#########.#.#.#.#######.###.#.###.#####.#.###N###.#.### +#.#.#...#.....#...#...#.#...........#.#.#.......#.#...#.#...#...#.#.#...#...#...# +#.#.#.#.#.#####.###.###.###########.#.#.#.#####.#.#.###.#.###.#.###.#.###.###.#.# +#...#.#.#.#...#.....#.#.#...#.......#...#.#..r#...#.#...#.....#.....#.#.#...#.#.# +#######.#.###.#######.#.###.#.#######.###.###.#######.#.#############.#.###.#.#.# +#.......#...#.......#.#...#.#.#.....#.#.#.....#.......#.......#.......#...#.#.#.# +###.###.###.#.#####.#.###.#.#.###.#.#.#.#####.#.#.###########.###.#####.#.#.###.# +#...#...#...#...#.#.#.......#...#.#.#.#.#.#...#.#...#...#...#...#.#.....#.#...#.# +#D###.###.###.#.#.#.###########.#.###.#.#.#.###.###.#.#.#.#.###.#.#.###.#####.#.# +#...#...#.#...#.#.#...........#.#.#...#.#...#...#...#.#...#...#...#.#.#.......#.# +#.#.#####.#####.#.###########.#.#.#.###.#.#######.###.#######.#####.#.#########.# +#.#.............#...............#.................#.........#.................M.# +#######################################.@.####################################### +#.#...#.......#...............................#.........#.........#..d#...#.....# +#.#.#.#.#####.#.#######################.###.#.#.#######.#.###.###.###.#.#.#.#.#K# +#...#...#...#.#.#...#.................#.#...#.#.#.#.....#...#.#.#.....#.#...#.#.# +#.#########.#.#.#.#.#.###############.#.#.###.#.#.#.#########.#.#####.#.#####.### +#.....#...#.#.#.#.#.#.#...#.........#...#.#...#.#.#.......#...#.#.....#.....#...# +#####.#.#.#.#.#.#.#.#.#.#.#.#####.#####.#.#.###.#.#######.#.###.#.#########.###.# +#...#.#.#...#.....#.#.#.#.#...#...#...#.#.#.#...#...........#.....#.......#.#...# +#.#.#.#.###.#########.#.#.###.#####.#.#.#.#.###.###.###########.###.###.###.#.#.# +#.#.#...#.W...#.......#.#...#.......#.#.#.#...#...#.#...#.#...#.#...#l#.#...#.#.# +#.###.#########.#######.###.#.#######.#.#.###.###.#.#.#.#.#.#.###.###.#.#.###.#.# +#.....#.........#...#...#...#.......#...#.#...#...#...#...#.#.#.......#.#...#.#.# +#.#####.#######.#.#.#.###.###.#####.#####.###.#.#####.#####.#.#.#######.###.#.### +#.#...#.#.....#.#.#...#...#...#...#.....#...#.R.#...#.#.....#.....#...#...#x#...# +#.#.#.#.###.###.#.#####.#######.#.#######.#.#####.#.###.###########.#.#.###.###.# +#...#.#...#...#.#.#...#...#...#.#.#.....#.#.#.....#.#...#.#.........#.#.......#.# +#####.###.###.#.#.###.###.#.#.#.#.#.###.#.#.#.#####.#.###.#.#########.#.#######.# +#...#...#...#...#...#.......#...#.#...#.#.#...#.#...#.....#.#.......#.#.#.......# +###.###.###.###.###.#############.###.#.#.#####.#.#######.#.#.#.#####Y###.#####.# +#...#.....#...#...#.......#.#...#.....#.#.#..i..#.......#.#.#.#.......#...#.#...# +#.###.#######.###.#######.#.#.#.#######.#.#.###.#####.###.#Q###.#######.###.#.### +#...#.#.......#..n#.#.....#...#...#.....#.#.#...#...#.#...#.#.#.#...#...#.#...#.# +#.#.#.#.###.#####.#.#######.#####.#.#####.#.#.###.###.#.###.#.#.#.#.#.###.#.###.# +#.#...#.#...#...#.....#...#.#...#...#...#...#...#.#...#...#..o#...#.#.#.......#.# +#.#####.#####.#.#######.#.#.#.#.#####.#.#######.#.#.###.###########.#.#######.#.# +#.....#...#...#.#.......#.#.#.#.....#.#.#.....#.#.#.....#.........#.#.#...#.....# +#####L###.#.###.#.#######.#.###.###.###.###.#.#.#.#######.#.#####.#.#.#.#.####### +#...#.#...#.#...#...#...#.#...#.#.......#...#.#...#.....#.#.....#.#.#.#.#.......# +#.#.#.#.###.#.#.###.#.#.#.###.#.#######.#.#######.#.#.###.#####.###.#.#.#######.# +#.#.#.#.....#.#.#...#.#.....#.#.......#.#.......#...#.#...#..j..#..v#.........#.# +#.#.#.#######.#.#.###.#.#####.###.###.###.#####.###.###.###.###.#.###.#########.# +#.#.#.#...#...#.#...#.#.#...#...#...#...#...#.#.#...#...#.#.#...#.#.#.#.........# +###.#.#.#.#.#######.#.###.#.###.#####.#.###.#.#.#.###.###.#.#####.#.#.#.#######.# +#...#...#.#.........#.....#...#.....#f#.#.....#.....#e..#.#.......#.#.#.#.#...#.# +#.#######.#################.#.#####.###.#.#############A#.#########.#.#.#.#.#.#.# +#...#.....#.....#...#.....#.#.....#.....#...#...#.....#...#...#.......#.#.#.#..z# +#.#.#.#####.###.###.#.###C#.###########.###.#.#T#.#.#####.#.###.#######.#.#.##### +#.#.#.X.#k..#.#...#...#...#.....#.....#.#...#.#...#.......#.....#....p..#...#...# +#.#.###.###.#.###.#####.#.#####.#.#.###.#.###.###################.#######.###.#.# +#.#.........#...........#.....#...#.....#...#.....................#...........#.# +################################################################################# \ No newline at end of file diff --git a/inputs/18s1.txt b/inputs/18s1.txt new file mode 100644 index 0000000..e6da8d0 --- /dev/null +++ b/inputs/18s1.txt @@ -0,0 +1,3 @@ +######### +#b.A.@.a# +######### \ No newline at end of file diff --git a/inputs/18s2.txt b/inputs/18s2.txt new file mode 100644 index 0000000..7345fa0 --- /dev/null +++ b/inputs/18s2.txt @@ -0,0 +1,5 @@ +######################## +#f.D.E.e.C.b.A.@.a.B.c.# +######################.# +#d.....................# +######################## \ No newline at end of file diff --git a/inputs/18s3.txt b/inputs/18s3.txt new file mode 100644 index 0000000..91cceef --- /dev/null +++ b/inputs/18s3.txt @@ -0,0 +1,5 @@ +######################## +#...............b.C.D.f# +#.###################### +#.....@.a.B.c.d.A.e.F.g# +######################## \ No newline at end of file diff --git a/inputs/18s4.txt b/inputs/18s4.txt new file mode 100644 index 0000000..2933b26 --- /dev/null +++ b/inputs/18s4.txt @@ -0,0 +1,9 @@ +################# +#i.G..c...e..H.p# +########.######## +#j.A..b...f..D.o# +########@######## +#k.E..a...g..B.n# +########.######## +#l.F..d...h..C.m# +################# \ No newline at end of file diff --git a/inputs/18s5.txt b/inputs/18s5.txt new file mode 100644 index 0000000..3324799 --- /dev/null +++ b/inputs/18s5.txt @@ -0,0 +1,6 @@ +######################## +#@..............ac.GI.b# +###d#e#f################ +###A#B#C################ +###g#h#i################ +######################## \ No newline at end of file diff --git a/inputs/18s6.txt b/inputs/18s6.txt new file mode 100644 index 0000000..09f2afd --- /dev/null +++ b/inputs/18s6.txt @@ -0,0 +1,7 @@ +####### +#a.#Cd# +##...## +##.@.## +##...## +#cB#Ab# +####### \ No newline at end of file diff --git a/inputs/18s7.txt b/inputs/18s7.txt new file mode 100644 index 0000000..7910575 --- /dev/null +++ b/inputs/18s7.txt @@ -0,0 +1,7 @@ +############### +#d.ABC.#.....a# +######...###### +######.@.###### +######...###### +#b.....#.....c# +############### \ No newline at end of file diff --git a/inputs/18s8.txt b/inputs/18s8.txt new file mode 100644 index 0000000..cfce56c --- /dev/null +++ b/inputs/18s8.txt @@ -0,0 +1,7 @@ +############# +#DcBa.#.GhKl# +#.###...#I### +#e#d#.@.#j#k# +###C#...###J# +#fEbA.#.FgHi# +############# \ No newline at end of file diff --git a/inputs/18s9.txt b/inputs/18s9.txt new file mode 100644 index 0000000..46a19a5 --- /dev/null +++ b/inputs/18s9.txt @@ -0,0 +1,9 @@ +############# +#g#f.D#..h#l# +#F###e#E###.# +#dCba...BcIJ# +#####.@.##### +#nK.L...G...# +#M###N#H###.# +#o#m..#i#jk.# +############# \ No newline at end of file diff --git a/main.go b/main.go index cc71205..769eb88 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,7 @@ var dayMap = []day{ &days.Day15{}, &days.Day16{}, &days.Day17{}, + &days.Day18{}, } func main() { diff --git a/utilities/map.go b/utilities/map.go index 58e970f..7d33d53 100644 --- a/utilities/map.go +++ b/utilities/map.go @@ -15,3 +15,13 @@ func MapValues[T comparable, U any](m map[T]U) []U { } return r } + +// CopyMap returns a copy of the passed-in map. Note: currently only works if [U] +// is not a map or slice. +func CopyMap[T comparable, U any](m map[T]U) map[T]U { + r := make(map[T]U) + for k, v := range m { + r[k] = v + } + return r +} diff --git a/utilities/vector.go b/utilities/vector.go index 0a7bee1..03f8804 100644 --- a/utilities/vector.go +++ b/utilities/vector.go @@ -44,11 +44,16 @@ func (v Vec2[T]) Equals(other Vec2[T]) bool { v.Y == other.Y } +func (v Vec2[T]) ManhattanDistance(other Vec2[T]) T { + return T(math.Abs(float64(v.X-other.X)) + math.Abs(float64(v.Y-other.Y))) +} + func VecBetween[T Number](a, b Vec2[T]) Vec2[T] { - return Vec2[T]{ - X: a.X - b.X, - Y: a.Y - b.Y, - } + return a.To(b) +} + +func ManhattanDistance[T Number](a, b Vec2[T]) T { + return a.ManhattanDistance(b) } func (v Vec3[T]) Dot(other Vec3[T]) T {