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

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
}