package day18 import ( "fmt" "math" "strconv" "strings" ) func matrixSize(lines []string) (int, int, int, int) { currRow, currCol := 0, 0 maxRow, maxCol := 0, 0 minRow, minCol := math.MaxInt32, math.MaxInt32 for _, line := range lines { fields := strings.Fields(line) dir := fields[0][0] dist, _ := strconv.Atoi(fields[1]) switch dir { case 'R': currCol += dist case 'L': currCol -= dist case 'D': currRow += dist case 'U': currRow -= dist } if currCol < minCol { minCol = currCol } if currRow < minRow { minRow = currRow } if currCol > maxCol { maxCol = currCol } if currRow > maxRow { maxRow = currRow } } fmt.Println("(", minRow, ",", minCol, ") - (", maxRow, ",", maxCol, ")") return minCol, minRow, maxRow, maxCol } func floodFill(matrix [][]int64, row int, col int, prevColor int64, newColor int64, height int, width int) { if row < 0 || row >= height || col < 0 || col >= width { return } if matrix[row][col] != prevColor { return } if matrix[row][col] == newColor { return } matrix[row][col] = newColor floodFill(matrix, row+1, col, prevColor, newColor, height, width) floodFill(matrix, row-1, col, prevColor, newColor, height, width) floodFill(matrix, row, col+1, prevColor, newColor, height, width) floodFill(matrix, row, col-1, prevColor, newColor, height, width) } func Part1(lines []string, printMatrix bool) int64 { minCol, minRow, maxRow, maxCol := matrixSize(lines) height, width := maxRow-minRow+1, maxCol-minCol+1 matrix := make([][]int64, height) for i := range matrix { matrix[i] = make([]int64, width) } dRow := minRow * -1 dCol := minCol * -1 currRow := dRow currCol := dCol // dig path for _, line := range lines { fields := strings.Fields(line) dir := fields[0][0] dist, _ := strconv.Atoi(fields[1]) color, _ := strconv.ParseInt(fields[2][2:len(fields[2])-3], 16, 64) switch dir { case 'R': for col := 1; col <= dist; col++ { matrix[currRow][currCol+col] = color } currCol += dist case 'L': for col := 1; col <= dist; col++ { matrix[currRow][currCol-col] = color } currCol -= dist case 'D': for row := 1; row <= dist; row++ { matrix[currRow+row][currCol] = color } currRow += dist case 'U': for row := 1; row <= dist; row++ { matrix[currRow-row][currCol] = color } currRow -= dist } } floodFill(matrix, dRow+1, dCol+1, 0, 1, height, width) //print matrix if printMatrix { for _, l := range matrix { for _, c := range l { v := 0 if c > 0 { v = 1 } fmt.Print(v, " ") } fmt.Println("") } } result := int64(0) for _, l := range matrix { for _, c := range l { if c > 0 { result += 1 } } } return result } /* type Fraction struct { num int64 den int64 } type ETentry struct { yMin int64 yMax int64 x_of_yMin int64 inv_slope Fraction } type AETentry struct { y int64 yMax int64 x int64 inv_slope Fraction } func buildEdgeTable(lines []string) []*ETentry { result := []*ETentry{} currCol, currRow := int64(0), int64(0) for _, line := range lines { fields := strings.Fields(line) dist, _ := strconv.ParseInt(fields[2][2:7], 16, 64) dir := fields[2][7:8] prevCol, prevRow := currCol, currRow switch dir { case "0": currCol += dist case "2": currCol -= dist case "1": currRow += dist case "3": currRow -= dist } et := ETentry{} if currRow < prevRow { et.yMin = currRow et.yMax = prevRow et.x_of_yMin = currCol } else { et.yMin = prevRow et.yMax = currRow et.x_of_yMin = prevCol } et.inv_slope = Fraction{ num: currCol - prevCol, den: currRow - prevRow, } result = append(result, &et) } slices.SortFunc(result, func(a, b *ETentry) int { if a.yMin < b.yMin { return -1 } else if a.yMin > b.yMin { return 1 } else { return 0 } }) return result } // Function to implement scan-line polygon filling func scanFill2(x, y []int64, edges int64, matrix [][]int64) int64 { result := int64(0) var i, j int64 xmin := int64(math.MaxInt64) xmax := int64(0) // Find the minimum and maximum x-coordinates of the polygon for i = 0; i < edges; i++ { if x[i] < xmin { xmin = x[i] } if x[i] > xmax { xmax = x[i] } } // Scan each scan-line within the polygon's vertical extent for i = xmin; i <= xmax; i++ { interPoints := make([]int64, 0, edges) count := int64(0) for j = 0; j < edges; j++ { next := (j + 1) % edges // Check if the current edge intersects with the scan line if (y[j] > i && y[next] <= i) || (y[next] > i && y[j] <= i) { interPoint := x[j] + (i-y[j])*(x[next]-x[j])/(y[next]-y[j]) interPoints = append(interPoints, interPoint) count++ } } // Sort the intersection points in ascending order sort.Slice(interPoints, func(i, j int) bool { return i < j }) // Fill the pixels between pairs of intersection points for j = 0; j < count; j += 2 { //line(interPoints[j], i, interPoints[j+1], i) for r := interPoints[j] + 1; r < interPoints[j+1]; r++ { matrix[r][i] = 1 } line := interPoints[j+1] - interPoints[j] + 1 result += line } } return result } // Edge represents an edge of the polygon type Edge struct { x1, y1, x2, y2 int64 } // NewEdge creates a new Edge given two points func NewEdge(x1, y1, x2, y2 int64) Edge { if y1 > y2 { return Edge{x1, y1, x2, y2} } return Edge{x2, y2, x1, y1} } // scanLineFill performs the scanline polygon fill algorithm func scanLineFill(x, y []int64, matrix [][]int64) { edges := make([]Edge, len(x)) for i := 0; i < len(x); i++ { nextIndex := (i + 1) % len(x) edges[i] = NewEdge(x[i], y[i], x[nextIndex], y[nextIndex]) } minY := edges[0].y2 maxY := edges[0].y1 for _, e := range edges { if e.y1 > maxY { maxY = e.y1 } if e.y2 < minY { minY = e.y2 } } for y := minY; y <= maxY; y++ { var xIntersections []int64 for _, e := range edges { if y > e.y2 && y <= e.y1 { if e.y1 != e.y2 { xIntersect := e.x2 + (y-e.y2)*(e.x1-e.x2)/(e.y1-e.y2) xIntersections = append(xIntersections, xIntersect) } } } slices.Sort(xIntersections) for i := 0; i < len(xIntersections); i += 2 { if i+1 < len(xIntersections) { for x := xIntersections[i]; x < xIntersections[i+1]; x++ { matrix[x][y] = 1 } } } } } type Point struct { X, Y int64 } // Edge represents an edge of a polygon type Edge2 struct { ymax, xofymin, slopeInverse float64 } // Function to fill a polygon using the scanline algorithm func ScanlineFill(matrix [][]int, polygon []Point, fillCol int) { if len(polygon) < 3 { return } // Function to create edges from the polygon vertices createEdges := func(polygon []Point) []Edge2 { var edges []Edge2 for i := 0; i < len(polygon); i++ { p1 := polygon[i] p2 := polygon[(i+1)%len(polygon)] if p1.Y == p2.Y { continue // Skip horizontal edges } edge := Edge2{} if p1.Y < p2.Y { edge.ymax = float64(p2.Y) edge.xofymin = float64(p1.X) edge.slopeInverse = float64(p2.X-p1.X) / float64(p2.Y-p1.Y) } else { edge.ymax = float64(p1.Y) edge.xofymin = float64(p2.X) edge.slopeInverse = float64(p1.X-p2.X) / float64(p1.Y-p2.Y) } edges = append(edges, edge) } return edges } edges := createEdges(polygon) // Find the min and max Y values minY := polygon[0].Y maxY := polygon[0].Y for _, p := range polygon { if p.Y < minY { minY = p.Y } if p.Y > maxY { maxY = p.Y } } // Initialize Active Edge Table (AET) var AET []Edge2 // Iterate through each scanline for y := minY; y <= maxY; y++ { // Remove edges where ymax is the current y for i := 0; i < len(AET); i++ { if AET[i].ymax == float64(y) { AET = append(AET[:i], AET[i+1:]...) i-- } } // Add edges where ymin is the current y for _, e := range edges { if e.xofymin == float64(y) { AET = append(AET, e) } } // Sort AET on xofymin sort.Slice(AET, func(i, j int) bool { return AET[i].xofymin < AET[j].xofymin }) // Fill pixels between pairs of intersections for i := 0; i < len(AET); i += 2 { if len(AET) > 1 { for x := AET[i].xofymin; x <= AET[i+1].xofymin; x++ { //img.Set(int(x), y, fillCol) matrix[int(x)][y] = fillCol } } } // Update xofymin for edges in AET for i := range AET { AET[i].xofymin += AET[i].slopeInverse } } } */ type Point struct { X, Y float64 } // Function to calculate the area of a polygon using the Shoelace algorithm func ShoelaceArea(polygon []Point) float64 { if len(polygon) < 3 { return 0.0 // Not a polygon } var area float64 = 0.0 j := len(polygon) - 1 // The last vertex is the 'previous' one to the first for i := 0; i < len(polygon); i++ { area += (polygon[j].X + polygon[i].X) * (polygon[j].Y - polygon[i].Y) j = i // j is previous vertex to i } return 0.5 * abs(area) } // Helper function to calculate the absolute value func abs(x float64) float64 { if x < 0 { return -x } return x } func Part2(lines []string) int64 { minCol, minRow, maxRow, maxCol := matrixSize(lines) height, width := maxRow-minRow+1, maxCol-minCol+1 matrix := make([][]int, height) for i := range matrix { matrix[i] = make([]int, width) } x := []int64{} y := []int64{} points := []Point{} currCol, currRow := int64(0), int64(0) x = append(x, currRow) y = append(y, currCol) points = append(points, Point{ X: float64(currRow), Y: float64(currCol), }) edges := int64(0) totalDig := int64(0) for _, line := range lines { fields := strings.Fields(line) dist, _ := strconv.ParseInt(fields[2][2:7], 16, 64) dir := fields[2][7:8] switch dir { case "0": currCol += dist case "2": currCol -= dist case "1": currRow += dist case "3": currRow -= dist } totalDig += int64(dist) x = append(x, currRow) y = append(y, currCol) points = append(points, Point{ X: float64(currRow), Y: float64(currCol), }) edges++ } result := int64(ShoelaceArea(points)) //ScanlineFill(matrix, points, 1) //+ totalDig fmt.Println(result) fmt.Println(totalDig) /* for _, l := range matrix { for _, c := range l { fmt.Print(c, " ") } fmt.Println("") } */ return result + totalDig/2 + 1 }