Advent-of-Code-2023/day24/lines.go

234 lines
5.8 KiB
Go

package day24
import (
"log"
"os"
"strconv"
"strings"
)
const (
// CoordMin int = 7
// CoordMax int = 27
CoordMin int = 200000000000000
CoordMax int = 400000000000000
)
type Point struct {
x, y, Z int
}
type HailParam struct {
p0, p1 Point
Dx, Dy, Dz int
line string
// for 2d : ay + bx = 0
a, b, c int
// for 2d : y = slope*x + shift
slope, shift float64
}
func CheckPairwiseIntersections(hails []HailParam) (totalIntersections int) {
for i, hail := range hails {
for j := i + 1; j < len(hails); j++ {
otherHail := hails[j]
intersect := CheckTaskIntersection(hail, otherHail)
if intersect {
totalIntersections += 1
}
}
}
return
}
func CheckTaskIntersection(h1, h2 HailParam) (doIntersect bool) {
log.Printf("intersecting %+v and %+v\n", h1, h2)
// x, y, intersectAtAll := IntersectByLineEquasions(h1, h2)
// x, y, intersectAtAll := IntersectByTwoPoints(h1, h2)
// x, y, intersectAtAll := IntersectByTwoPointsAttempt2(h1, h2)
// x, y, intersectAtAll := IntersectByTwoPointsAttempt3(h1, h2)
x, y, intersectAtAll := IntersectBySlopeAndShift(h1, h2)
if !intersectAtAll {
log.Println("no intersection at all\n", x, y)
return false
}
isH1Future := h1.FloatPointInFuture(x, y)
isH2Future := h2.FloatPointInFuture(x, y)
if !isH1Future {
log.Printf("point %f, %f in the past for h1\n", x, y)
}
if !isH2Future {
log.Printf("point %f, %f in the past for h2\n", x, y)
}
if !isH1Future || !isH2Future {
return false
}
if x < float64(CoordMin) || x > float64(CoordMax) ||
y < float64(CoordMin) || y > float64(CoordMax) {
log.Printf("intersect at %f %f but outside of area\n", x, y)
return false // outside of area
}
log.Println("> intersect inside of the area! ", x, y)
return true
}
// WRONG
func IntersectByLineEquasions(h1, h2 HailParam) (intersectionX, intersectionY float64, isIntersecting bool) {
D := h1.a*h2.b - h1.b*h2.a
if D == 0 {
return 0, 0, false
}
Dx := h1.c*h2.b - h1.b*h2.c
Dy := h1.a*h2.c - h1.b*h2.a
intersectionX = float64(Dx) / float64(D)
intersectionY = float64(Dy) / float64(D)
isIntersecting = true
return
}
// WRONG
// in 2d only
func IntersectByTwoPoints(h1, h2 HailParam) (intersectionX, intersectoinY float64, isIntersecting bool) {
p1 := h1.p0
p2 := h1.p1
p3 := h2.p0
p4 := h2.p1
denominator := (p1.x-p2.x)*(p3.y-p4.y) - (p1.y-p2.y)*(p3.x-p4.x)
if denominator == 0 {
return 0, 0, false
}
divisibleX := (p1.x*p2.y-p1.y*p2.x)*(p3.x-p4.x) - (p1.x-p2.x)*(p3.x*p4.y-p3.y*p4.x)
divisibleY := (p1.x*p2.y-p1.y*p2.x)*(p3.y-p4.y) - (p1.y-p2.y)*(p3.x*p4.y-p3.y*p4.x)
x := float64(divisibleX) / float64(denominator)
y := float64(divisibleY) / float64(denominator)
return x, y, true
}
func IntersectByTwoPointsAttempt2(h1, h2 HailParam) (intersectionX, intersectionY float64, isIntersecting bool) {
x1 := h1.p0.x
y1 := h1.p0.y
x2 := h1.p1.x
y2 := h1.p1.y
x3 := h2.p0.x
y3 := h2.p0.y
x4 := h2.p1.x
y4 := h2.p1.y
divisor := (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4)
if divisor == 0 {
return
}
intersectionX = float64((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)) / float64(divisor)
intersectionY = float64((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)) / float64(divisor)
isIntersecting = true
return
}
func determinant(a, b Point) int {
return a.x * b.y - a.y * b.x
}
func IntersectByTwoPointsAttempt3(h1, h2 HailParam) (intersectionX, intersectionY float64, isIntersecting bool) {
xdiff := Point{ h1.p0.x - h1.p1.x, h2.p0.x - h2.p1.x, 0 }
ydiff := Point { h1.p0.y - h1.p1.y, h2.p0.y - h2.p1.y , 0 }
div := determinant(xdiff, ydiff)
if div == 0 {
return
}
d := Point { determinant(h1.p0, h1.p1), determinant(h2.p0, h2.p1), 0}
x := float64(determinant(d, xdiff)) / float64(div)
y := float64(determinant(d, ydiff)) / float64(div)
return x, y, true
}
func IntersectBySlopeAndShift(h1, h2 HailParam) (intersectionX, intersectionY float64, isIntersecting bool) {
if h1.slope == h2.slope {
return
}
// y = slope * x + shift
// slope1 * x + shift1 = slope2 * x + shift2
// x = ( shift2 - shift1 ) / (slope1 - slope2)
x := (h2.shift - h1.shift) / (h1.slope - h2.slope)
y := h1.slope * x + h1.shift
return x, y, true
}
func (h HailParam) PointInFuture(p Point) bool {
xPositiveSteps := (p.x-h.p0.x)*h.Dx >= 0
yPositiveSteps := (p.y-h.p0.y)*h.Dy >= 0
zPositiveSteps := (p.Z-h.p0.Z)*h.Dz >= 0
return xPositiveSteps && yPositiveSteps && zPositiveSteps
}
func (h HailParam) FloatPointInFuture(x, y float64) bool {
xPositiveSteps := (x-float64(h.p0.x))*float64(h.Dx) >= 0
// yPositiveSteps := (y - float64(h.p0.y)) * float64(h.Dy) >= 0
// return xPositiveSteps && yPositiveSteps
return xPositiveSteps
}
// 19, 13, 30 @ -2, 1, -2
func ReadHailLine(line string) (h HailParam) {
h.line = line
line = strings.ReplaceAll(line, "@", "")
line = strings.ReplaceAll(line, ",", "")
fields := strings.Fields(line)
h.p0.x = AtoIOrPanic(fields[0])
h.p0.y = AtoIOrPanic(fields[1])
h.p0.Z = AtoIOrPanic(fields[2])
h.Dx = AtoIOrPanic(fields[3])
h.Dy = AtoIOrPanic(fields[4])
h.Dz = AtoIOrPanic(fields[5])
countP1AfterMillis := 1
h.p1.x = h.p0.x + countP1AfterMillis * h.Dx
h.p1.y = h.p0.y + countP1AfterMillis * h.Dy
h.p1.Z = h.p0.Z + countP1AfterMillis * h.Dz
h.a = h.p0.y - h.p1.y
h.b = h.p1.x - h.p0.x
h.c = -(h.p0.x*h.p1.y - h.p1.x*h.p0.y)
h.slope = float64(h.Dy) / float64(h.Dx)
// y = slope * x + shift
// shift = y - slope * x // for some point
h.shift = float64(h.p0.y) - h.slope * float64(h.p0.x)
return
}
func ReadHailFile(filename string) []HailParam {
bytes, err := os.ReadFile(filename)
if err != nil {
panic(err)
}
text := strings.TrimSpace(string(bytes))
lines := strings.Split(text, "\n")
result := make([]HailParam, len(lines))
for i, line := range lines {
result[i] = ReadHailLine(line)
}
return result
}
func AtoIOrPanic(str string) (num int) {
num, err := strconv.Atoi(str)
if err != nil {
panic(err)
}
return
}