diff --git a/2021/day17/main.go b/2021/day17/main.go new file mode 100644 index 0000000..c598003 --- /dev/null +++ b/2021/day17/main.go @@ -0,0 +1,107 @@ +package main + +import ( + "fmt" + "regexp" + + "github.com/jdkaplan/advent-of-code/aoc" +) + +func main() { + text := aoc.Input().ReadFile("day17.txt") + re := regexp.MustCompile(`^target area: x=(-?\d+)\.\.(-?\d+), y=(-?\d+)..(-?\d+)$`) + m := re.FindStringSubmatch(text) + var ( + x1 = aoc.MustInt(m[1]) + x2 = aoc.MustInt(m[2]) + y1 = aoc.MustInt(m[3]) + y2 = aoc.MustInt(m[4]) + ) + r := Region{x1, x2, y1, y2} + fmt.Println(part1(r)) +} + +type Region struct{ x1, x2, y1, y2 int } + +type Position struct{ x, y int } + +func (p Position) Move(v Velocity) Position { + return Position{p.x + v.x, p.y + v.y} +} + +func (p Position) InRegion(r Region) bool { + return r.x1 <= p.x && p.x <= r.x2 && r.y1 <= p.y && p.y <= r.y2 +} + +func (p Position) Overshot(r Region) bool { + return p.x > r.x2 || p.y < r.y1 +} + +type Velocity struct{ x, y int } + +func (v Velocity) Drag() Velocity { + x := v.x + if x < 0 { + x++ + } else if x > 0 { + x-- + } + return Velocity{x, v.y - 1} +} + +func part1(r Region) int { + vxmin, vxmax := vxrange(r.x1, r.x2) + vymin, vymax := vyrange(r.y1, r.y2) + hmax := 0 + for vx := vxmin; vx <= vxmax; vx++ { + for vy := vymin; vy <= vymax; vy++ { + hit := simulate(Velocity{vx, vy}, r) + if !hit { + continue + } + if h := tri(vy); h > hmax { + hmax = h + } + } + } + return hmax +} + +func simulate(v Velocity, r Region) bool { + p := Position{0, 0} + for !p.InRegion(r) && !p.Overshot(r) { + p = p.Move(v) + v = v.Drag() + } + return p.InRegion(r) +} + +func vxrange(x1, x2 int) (int, int) { + // Zoom directly to the far edge. + vxmax := x2 + // Find the first value that crosses the near edge. + vxmin := 0 + for vx := 0; vx < vxmax; vx++ { + xterm := tri(vx) + if xterm >= x1 { + vxmin = vx + break + } + } + return vxmin, vxmax +} + +func vyrange(y1, y2 int) (int, int) { + if y1 >= 0 { + panic("This only works for negative-y regions!") + } + // Zoom directly to the close edge. + vymin := -y2 + // Zoom directly to the far edge. + vymax := -y1 + return vymin, vymax +} + +func tri(x int) int { + return (x * (x + 1)) / 2 +}