2021: d17: ex1: add solution
This commit is contained in:
parent
b41103b3a8
commit
b8e0bc7db7
111
2021/d17/ex1/ex1.py
Executable file
111
2021/d17/ex1/ex1.py
Executable file
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import itertools
|
||||
import math
|
||||
import sys
|
||||
from typing import Iterator, List, NamedTuple
|
||||
|
||||
|
||||
class Point(NamedTuple):
|
||||
x: int
|
||||
y: int
|
||||
|
||||
|
||||
class Probe(NamedTuple):
|
||||
position: Point
|
||||
velocity: Point
|
||||
|
||||
|
||||
class Area(NamedTuple):
|
||||
min: Point
|
||||
max: Point
|
||||
|
||||
|
||||
def solve(input: List[str]) -> int:
|
||||
def parse(line: str) -> Area:
|
||||
x_range = line.split("x=")[1].split(",")[0]
|
||||
y_range = line.split("y=")[1]
|
||||
|
||||
min_x, max_x = map(int, x_range.split(".."))
|
||||
min_y, max_y = map(int, y_range.split(".."))
|
||||
|
||||
# Sanity check
|
||||
assert min_x <= max_x
|
||||
assert min_y <= max_y
|
||||
|
||||
return Area(Point(min_x, min_y), Point(max_x, max_y))
|
||||
|
||||
def trajectory(p: Probe) -> Iterator[Probe]:
|
||||
def step(p: Probe) -> Probe:
|
||||
def drag(x: int) -> int:
|
||||
if x < 0:
|
||||
return x + 1
|
||||
if x > 0:
|
||||
return x - 1
|
||||
return 0
|
||||
|
||||
def gravity(y: int) -> int:
|
||||
return y - 1
|
||||
|
||||
pos, vel = p
|
||||
|
||||
new_pos = Point(pos.x + vel.x, pos.y + vel.y)
|
||||
new_vel = Point(drag(vel.x), gravity(vel.y))
|
||||
|
||||
return Probe(new_pos, new_vel)
|
||||
|
||||
while True:
|
||||
yield (p := step(p))
|
||||
|
||||
def hits_target(probe: Probe, area: Area) -> bool:
|
||||
# Too lazy to find an actual good condition on this loop, early break is enough
|
||||
for p in trajectory(probe):
|
||||
x, y = p.position
|
||||
# Early exit when we cannot possibly get to the area
|
||||
if y < area.min.y and p.velocity.y <= 0:
|
||||
break
|
||||
if x < area.min.x and p.velocity.x <= 0:
|
||||
break
|
||||
if x > area.max.x and p.velocity.x >= 0:
|
||||
break
|
||||
# Keep going if we're not in bounds
|
||||
if x < area.min.x or x > area.max.x:
|
||||
continue
|
||||
if y < area.min.y or y > area.max.y:
|
||||
continue
|
||||
# We are in the area
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_velocities(area: Area) -> Iterator[Point]:
|
||||
position = Point(0, 0)
|
||||
assert area.min.y < 0 # Sanity check, due to lower bound in loop
|
||||
|
||||
# Can't overshoot after a single step
|
||||
for vx in range(0, area.max.x + 1):
|
||||
# Can't overshoot after a single step, symmetric velocity when coming down
|
||||
for vy in range(area.min.y, abs(area.min.y) + 1):
|
||||
velocity = Point(vx, vy)
|
||||
if hits_target(Probe(position, velocity), area):
|
||||
yield velocity
|
||||
|
||||
def highest_point(velocity: Point) -> Point:
|
||||
# When the y velocity is negative, the height can only go down
|
||||
of_interest = itertools.takewhile(
|
||||
lambda p: p.velocity.y >= 0, trajectory(Probe(Point(0, 0), velocity))
|
||||
)
|
||||
points = [p.position for p in of_interest]
|
||||
return max(points, key=lambda p: p.y, default=Point(0, 0))
|
||||
|
||||
target_area = parse(input[0])
|
||||
velocities = set(find_velocities(target_area))
|
||||
return max(highest_point(v).y for v in velocities)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
input = [line.strip() for line in sys.stdin.readlines()]
|
||||
print(solve(input))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue