package main import ( "bufio" "fmt" "log" "math" "os" "sort" "strings" ) type Point struct { x, y int } func distance(a, b Point) float64 { return math.Sqrt(math.Pow(math.Abs(float64(b.x-a.x)), 2) + math.Pow(math.Abs(float64(b.y-a.y)), 2)) } func distanceToLine(a, b, c Point) float64 { px := b.x - a.x py := b.y - a.y norm := (px * px) + (py * py) u := float64(((c.x-a.x)*px)+((c.y-a.y)*py)) / float64(norm) if u > 1 { u = 1 } else if u < 0 { u = 0 } x := float64(a.x) + u*float64(px) y := float64(a.y) + u*float64(py) dx := x - float64(c.x) dy := y - float64(c.y) return math.Sqrt((dx * dx) + (dy * dy)) } func FindBestPosition(grid [][]rune, debug bool) (Point, int) { var result Point var max int points := make([]Point, 0) for y := range grid { for x := range grid[y] { if grid[y][x] == '#' { points = append(points, Point{x: x, y: y}) } } } for i := range points { var count int for j := range points { if i == j { continue } p1, p2 := points[i], points[j] if p2.x < p1.x { p1, p2 = p2, p1 } var hit bool for k := range points { if k == i || k == j { continue } if distanceToLine(p1, p2, points[k]) == 0 { hit = true break } } if hit { continue } count++ } if debug { grid[points[i].y][points[i].x] = rune(count) } if count > max { max = count result = points[i] } } return result, max } type Asteroid struct { pos Point distance float64 } func VaporizeAsteroids(grid [][]rune, station Point, debug bool) Point { radDeg := 180.0 / math.Pi angles := make([]float64, 0) angleDistances := make(map[float64][]Asteroid) for y := range grid { for x := range grid[y] { if x == station.x && y == station.y { continue } if grid[y][x] == '#' { angle := math.Atan2(float64(x-station.x), -float64(y-station.y)) * radDeg if angle < 0 { angle += 360.0 } if _, ok := angleDistances[angle]; !ok { angleDistances[angle] = make([]Asteroid, 0) angles = append(angles, angle) } p := Point{ x: x, y: y, } angleDistances[angle] = append(angleDistances[angle], Asteroid{ pos: p, distance: distance(station, p), }) } } } sort.Slice(angles, func(a, b int) bool { return angles[a] < angles[b] }) for i := range angleDistances { sort.Slice(angleDistances[i], func(a, b int) bool { return angleDistances[i][a].distance < angleDistances[i][b].distance }) } if debug { for _, a := range angles { fmt.Printf("%f -> [\n", a) for _, as := range angleDistances[a] { fmt.Printf("\t%+v\n", as) } fmt.Println("]") } fmt.Println("Vaporizing...") } var count int for count < 200 { for _, a := range angles { if len(angleDistances[a]) == 0 { continue } as := angleDistances[a][0] angleDistances[a] = angleDistances[a][1:] if debug { fmt.Printf("Asteroid #%d, %+v\n", count+1, as) } count++ if count == 200 { return as.pos } } } return Point{} } func main() { input, err := os.Open("input.txt") if err != nil { log.Fatal(fmt.Errorf("could not open input file: %w", err)) } defer input.Close() scanner := bufio.NewScanner(input) scanner.Split(bufio.ScanLines) grid := make([][]rune, 0) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) x := make([]rune, len(line)) for i := range line { x[i] = rune(line[i]) } grid = append(grid, x) } pos, count := FindBestPosition(grid, false) fmt.Printf("Part 1: %d\n", count) pos = VaporizeAsteroids(grid, pos, false) fmt.Printf("Part 2: %d\n", (pos.x*100)+pos.y) }