1+ # Advent of code Day 09
2+ # https://adventofcode.com/2025/day/9
3+ # 09/12/2025
4+
5+ E = 10 ** - 3
6+
7+ class Line :
8+ def __init__ (self , p1 , p2 ):
9+ x1 , y1 = p1
10+ x2 , y2 = p2
11+ if x1 == x2 :
12+ self .a , self .b , self .c = 1 , 0 , x1
13+ else :
14+ a = (y1 - y2 )/ (x1 - x2 )
15+ b = (x2 * y1 - x1 * y2 )/ (x2 - x1 )
16+ self .a , self .b , self .c = a , - 1 , - b
17+ self .line = self
18+
19+ def has (self , p ):
20+ if not p : return False
21+ x , y = p
22+ return abs (x * self .a + y * self .b - self .c ) <= E
23+
24+ def inter (self , other ):
25+ a , b , p = self .a , self .b , self .c
26+ c , d , q = other .a , other .b , other .c
27+
28+ det = a * d - b * c
29+ if abs (det ) <= E : return None
30+ a , b , c , d = d , - b , - c , a
31+ x = a * p + b * q
32+ y = c * p + d * q
33+ return (x / det , y / det )
34+
35+
36+ class Ray :
37+ def __init__ (self , line , condition ):
38+ self .line = line
39+ self .condition = condition
40+
41+ def has (self , p ):
42+ if not p : return False
43+ if not self .line .has (p ): return False
44+ return self .condition (p )
45+
46+ def inter (self , ray ):
47+ inter = self .line .inter (ray .line )
48+ return self .has (inter ) and ray .has (inter )
49+
50+ with open ("input.txt" ) as file :
51+ inp = list (map (lambda s : tuple (map (int , s .strip ().split ("," ))), file .readlines ()))
52+
53+ from tqdm .auto import tqdm
54+ from functools import cache
55+ from itertools import combinations
56+
57+
58+ xs = sorted (set (x for x , y in inp ))
59+ ys = sorted (set (y for x , y in inp ))
60+
61+ original = inp
62+ inp = [(xs .index (x ), ys .index (y )) for x , y in inp ]
63+
64+ edges = []
65+ for i , (x1 , y1 ) in enumerate (inp ):
66+ x2 , y2 = inp [(i + 1 ) % len (inp )]
67+
68+ line = Line ((x1 , y1 ), (x2 , y2 ))
69+ if x1 == x2 :
70+ def hits (p , Y1 = y1 , Y2 = y2 ):
71+ _ , y = p
72+ return min (Y1 , Y2 ) <= y <= max (Y1 , Y2 )
73+ elif y1 == y2 :
74+ def hits (p , X1 = x1 , X2 = x2 ):
75+ x , _ = p
76+ return min (X1 , X2 ) <= x <= max (X1 , X2 )
77+
78+ edges .append (Ray (line , hits ))
79+
80+ @cache
81+ def inside (p ):
82+ # Evil ray algorithm
83+ # To be inside, the four rays must cross
84+ # something
85+ if p in inp : return True
86+ x , y = p
87+
88+ rays = [
89+ Ray (Line (p , (x - 10 , y )), lambda p1 , X = x : p1 [0 ] <= X ),
90+ Ray (Line (p , (x , y + 10 )), lambda p1 , Y = y : p1 [1 ] >= Y ),
91+ Ray (Line (p , (x + 10 , y )), lambda p1 , X = x : p1 [0 ] >= X ),
92+ Ray (Line (p , (x , y - 10 )), lambda p1 , Y = y : p1 [1 ] <= Y )
93+ ]
94+
95+ hits = [0 , 0 , 0 , 0 ]
96+ for edge in edges :
97+ for i , ray in enumerate (rays ):
98+ if ray .inter (edge ):
99+ hits [i ] = 1
100+ if sum (hits ) == 4 : return True
101+ return sum (hits ) == 4
102+
103+ res1 , res2 = 0 , 0
104+
105+ for (x1 , y1 ), (x2 , y2 ) in tqdm (list (combinations (inp , 2 ))):
106+ # area computed with real coords
107+ area = (abs (xs [x2 ] - xs [x1 ]) + 1 ) * (abs (ys [y2 ] - ys [y1 ]) + 1 )
108+ res1 = max (res1 , area )
109+
110+ # Checks on reduced coords
111+ allIn = True
112+ for x in range (min (x1 , x2 ), max (x1 , x2 ) + 1 ):
113+ for y in range (min (y1 , y2 ), max (y1 , y2 ) + 1 ):
114+ if not inside ((x , y )):
115+ allIn = False
116+ break
117+ if not allIn :
118+ break
119+
120+ if allIn :
121+ res2 = max (res2 , area )
122+
123+ print (res1 , res2 )
0 commit comments