Advent-of-Code/year25/day9/part2.go
2025-12-24 22:44:45 +02:00

195 lines
4.5 KiB
Go

package day9
import (
"errors"
"fmt"
"strconv"
"git.bizdoc.ro/gabi-public/Advent-of-Code.git/aoc"
"git.bizdoc.ro/private/devkit.git/collections/geometry/v2"
)
const DEBUG = false
var debugGrid geometry.Grid[byte]
func initDebugGrid(points [][]int) {
if !DEBUG {
return
}
var gridEnd geometry.Point
for _, point := range points {
gridEnd.Line = max(point[0], gridEnd.Line)
gridEnd.Col = max(point[1], gridEnd.Col)
}
gridEnd = gridEnd.Add(geometry.NewPoint(3, 3))
debugGrid = geometry.NewGrid[byte](gridEnd.Line, gridEnd.Col)
debugGrid.Fill('.')
}
func Part2(ctx aoc.Context) (result int, err error) {
// parse input
// generate all possible squares (check part 1)
// check if any line is cut by any polygon line
// check if all corners are inside the polygon
// check if corners are on the edge
g, err := geometry.ScannerToGrid(ctx.Scanner(), ",", strconv.Atoi)
if err != nil {
return 0, fmt.Errorf("day9: failed to parse intput %w", err)
}
points := geometry.UnsafeGridData(g)
for _, point := range points {
point[1], point[0] = point[0], point[1]
}
initDebugGrid(points)
mainPolygon := createMainPolygon(points)
rectangles := day9FindRectangles(points)
for _, rectangle := range rectangles {
rectangleVertices := rectangle.Vertexes()
if DEBUG {
dg := debugGrid.Clone()
for _, v := range rectangleVertices {
for a := v.Start; a != v.End; a = a.MoveDirection(v.Facing) {
dg.Set(a, '*')
}
}
fmt.Println(dg)
fmt.Println()
}
if !isP1InsideP2(rectangleVertices[:], mainPolygon.Vertices) {
continue
}
if polygonsIntersect(rectangleVertices[:], mainPolygon.Vertices) {
continue
}
return int(rectangle.Area), nil
}
return 0, errors.New("no solution found")
}
func isP1InsideP2(p1, p2 []Vertex) bool {
for _, rectangleVertex := range p1 {
corner := rectangleVertex.Start // for each point of retangle
// check if corner is on vertex. if yes we are fine
// count vertices
verticesPassed := 0
for _, v := range p2 {
if isPointOnLine(corner, v) {
verticesPassed = 1
break
}
if v.Start.Col == v.End.Col { // is vertical
if v.Start.Line <= corner.Line && corner.Line <= v.End.Line {
verticesPassed += 1
}
}
}
if verticesPassed&1 == 0 {
return false
}
}
return true
}
func isPointOnLine(p geometry.Point, l Vertex) bool {
v := p.Subs(l.Start)
w := l.End.Subs(l.Start)
if v.Line*w.Col != v.Col*w.Line {
return false
}
return w.Dot(p.Subs(l.End)) <= 0
}
func polygonsIntersect(p1, p2 []Vertex) bool {
for _, squareVertex := range p1 {
// vertices can touch
squareVertex.Start = squareVertex.Start.MoveDirection(squareVertex.Facing)
squareVertex.End = geometry.NewDirectionalPointFromPoint(squareVertex.End, squareVertex.Facing).MoveBackward().Point()
for _, mainVertex := range p2 {
if verticesIntersect(squareVertex, mainVertex) {
if DEBUG {
dg := debugGrid.Clone()
for a := squareVertex.Start; a != squareVertex.End; a = a.MoveDirection(squareVertex.Facing) {
dg.Set(a, '*')
}
for a := mainVertex.Start; a != mainVertex.End; a = a.MoveDirection(mainVertex.Facing) {
if dg.At(a) == '*' {
dg.Set(a, '+')
} else {
dg.Set(a, '@')
}
}
fmt.Println(dg)
fmt.Println()
}
return true
}
}
}
return false
}
type Polygon struct {
Vertices []Vertex
}
type Vertex struct {
Start geometry.Point
End geometry.Point
Facing geometry.Direction
}
func createMainPolygon(vertexes [][]int) (p Polygon) {
p.Vertices = make([]Vertex, 0, len(vertexes)+1)
prev := vertexes[len(vertexes)-1]
for i := range vertexes {
curr := vertexes[i]
v := Vertex{
Start: geometry.NewPoint(prev[0], prev[1]),
End: geometry.NewPoint(curr[0], curr[1]),
}
v.Facing = computeDirection(v.Start, v.End)
p.Vertices = append(p.Vertices, v)
prev = curr
}
return
}
func computeDirection(p1, p2 geometry.Point) geometry.Direction { // todo: move to devkit
switch {
case p1.Col == p2.Col:
switch {
case p1.Line == p2.Line:
panic("p1 and p2 do not form a line")
case p1.Line < p2.Line:
return geometry.DirectionDown
case p1.Line > p2.Line:
return geometry.DirectionUp
default:
panic("unreachable")
}
case p1.Line == p2.Line:
switch {
case p1.Col == p2.Col:
panic("p1 and p2 do not form a line")
case p1.Col < p2.Col:
return geometry.DirectionRight
case p1.Col > p2.Col:
return geometry.DirectionLeft
default:
panic("unreachable")
}
default:
panic("unreachable")
}
}