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) { 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] } if DEBUG { initDebugGrid(points) } mainPolygon := createMainPolygon(points) rectangles := day9FindRectangles(points) for _, rectangle := range rectangles { rectangleVertices := rectangle.Vertices() 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 // count vertices verticesPassed := 0 for _, v := range p2 { if isPointOnLine(corner, v) { return true } 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") } }