1
+ import sys
2
+ import gridutil .grid as grid
3
+ import gridutil .coord as coord
4
+ from collections import defaultdict
5
+ import os
6
+ from pathlib import Path
7
+ from PIL import Image
8
+ from tqdm import tqdm
9
+
10
+
11
+ def parse (instr : str ) -> grid .Grid :
12
+ return grid .parse (instr .upper ())
13
+
14
+ def one (instr : str ):
15
+ wordsearch = parse (instr )
16
+
17
+ seq_starts = list (
18
+ map (lambda x : x [0 ], filter (lambda x : x [1 ] == "X" , wordsearch .items ()))
19
+ )
20
+ detected_sequences = set ()
21
+
22
+ for start_pos in seq_starts :
23
+ for xdir in [- 1 , 0 , 1 ]:
24
+ for ydir in [- 1 , 0 , 1 ]:
25
+
26
+ if xdir == 0 and ydir == 0 :
27
+ continue
28
+
29
+ delta = coord .Coordinate (xdir , ydir )
30
+
31
+ ok = True
32
+ b = []
33
+ for i , v in enumerate ("XMAS" ):
34
+ if not ok :
35
+ break
36
+
37
+ x = coord .add (start_pos , coord .mult (delta , i ))
38
+ g = wordsearch .get (x , "-" )
39
+ ok = g == v
40
+ b .append (x )
41
+
42
+ if ok :
43
+ detected_sequences .add (tuple (b ))
44
+
45
+ return detected_sequences
46
+
47
+
48
+ def check_cross_adjacents (s : str ) -> bool :
49
+ return s == "SM" or s == "MS"
50
+
51
+
52
+ def two (instr : str ):
53
+ wordsearch = parse (instr )
54
+
55
+ seq_starts = list (
56
+ map (lambda x : x [0 ], filter (lambda x : x [1 ] == "A" , wordsearch .items ()))
57
+ )
58
+ detected_sequences = set ()
59
+
60
+ for start_pos in seq_starts :
61
+
62
+ a = wordsearch .get (coord .add (start_pos , (- 1 , - 1 )), "" ) + wordsearch .get (
63
+ coord .add (start_pos , (1 , 1 )), ""
64
+ )
65
+ b = wordsearch .get (coord .add (start_pos , (- 1 , 1 )), "" ) + wordsearch .get (
66
+ coord .add (start_pos , (1 , - 1 )), ""
67
+ )
68
+
69
+ if check_cross_adjacents (a ) and check_cross_adjacents (b ):
70
+ detected_sequences .add (start_pos )
71
+
72
+ return detected_sequences
73
+
74
+
75
+ lowest_colour = (255 , 245 , 237 )
76
+ highest_colour = (255 , 159 , 45 )
77
+ colour_diffs = tuple (map (lambda x : x [1 ] - x [0 ], zip (highest_colour , lowest_colour )))
78
+
79
+
80
+ def get_colour_for (n ):
81
+ return tuple (
82
+ map (int , map (lambda x : x [0 ] - x [1 ], zip (lowest_colour , map (lambda x : x * n , colour_diffs ))))
83
+ )
84
+
85
+
86
+ scale_factor = 4
87
+
88
+ def generate_frame (path : str , wordsearch : grid .Grid , counts : dict [coord .Coordinate , int ]):
89
+ max_val = max (counts .values ())
90
+
91
+ maxx , maxy = grid .get_max_x (wordsearch ), grid .get_max_y (wordsearch )
92
+
93
+ img = Image .new ("RGB" , (maxx + 1 , maxy + 1 ))
94
+
95
+ for x in range (maxx + 1 ):
96
+ for y in range (maxy + 1 ):
97
+ img .putpixel ((x , y ), get_colour_for (counts [(x , y )]/ max_val ))
98
+
99
+ img = img .resize ((maxx * scale_factor , maxy * scale_factor ), resample = Image .NEAREST )
100
+ img .save (path )
101
+
102
+
103
+ def main ():
104
+ inp = sys .stdin .read ().strip ()
105
+ wordsearch = parse (inp )
106
+
107
+ j = defaultdict (lambda : 0 )
108
+ for state in one (inp ):
109
+ for s in state :
110
+ j [s ] = j [s ] + 1
111
+ generate_frame ("heatmap-1.png" , wordsearch , j )
112
+
113
+ j = defaultdict (lambda : 0 )
114
+ for state in two (inp ):
115
+ for dir in [(0 , 0 ), (- 1 , - 1 ), (- 1 , 1 ), (1 , 1 ), (1 , - 1 )]:
116
+ s = coord .add (state , dir )
117
+ j [s ] = j [s ] + 1
118
+ generate_frame ("heatmap-2.png" , wordsearch , j )
119
+
120
+
121
+ if __name__ == "__main__" :
122
+ main ()
0 commit comments