You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
517 lines
10 KiB
Go
517 lines
10 KiB
Go
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
|
|
}
|