1+ require 'set'
2+
3+ class Day21
4+
5+ def part_one ( input )
6+ sequences = input . split ( "\n " )
7+ sim = DayOne . new ( sequences )
8+ sim . run!
9+ end
10+
11+ class DayOne
12+
13+ def initialize ( sequences )
14+ @sequences = sequences
15+
16+ @moves_cache = { }
17+ @scores_cache = { }
18+ end
19+
20+ # Define the numeric keypad with coordinates
21+ NUMPAD = {
22+ '7' => [ 0 , 0 ] ,
23+ '8' => [ 0 , 1 ] ,
24+ '9' => [ 0 , 2 ] ,
25+ '4' => [ 1 , 0 ] ,
26+ '5' => [ 1 , 1 ] ,
27+ '6' => [ 1 , 2 ] ,
28+ '1' => [ 2 , 0 ] ,
29+ '2' => [ 2 , 1 ] ,
30+ '3' => [ 2 , 2 ] ,
31+ ' ' => [ 3 , 0 ] ,
32+ '0' => [ 3 , 1 ] ,
33+ 'A' => [ 3 , 2 ]
34+ } . freeze
35+
36+ # Define the directional keypad movements with corresponding commands
37+ KEYPAD = {
38+ ' ' => [ 0 , 0 ] ,
39+ '^' => [ 0 , 1 ] ,
40+ 'A' => [ 0 , 2 ] ,
41+ '<' => [ 1 , 0 ] ,
42+ 'v' => [ 1 , 1 ] ,
43+ '>' => [ 0 , 2 ]
44+ } . freeze
45+
46+
47+ def xdir ( x )
48+ x > 0 ? 'v' : '^'
49+ end
50+
51+ def ydir ( y )
52+ y > 0 ? '>' : '<'
53+ end
54+
55+ def run!
56+ result = 0
57+ @sequences . each do |sequence |
58+ value = Float ::INFINITY
59+ 500 . times do
60+ @moves_cache . clear
61+ @scores_cache . clear
62+ value = [ value , score ( sequence , 3 , 0 ) * sequence [ 0 ..-2 ] . to_i ] . min
63+ end
64+ result += value
65+ end
66+ result
67+ end
68+
69+ def score ( sequence , depth , current = 0 )
70+ cache_key = [ sequence , depth , current ]
71+ # puts "scores_cache: #{@scores_cache}"
72+ return @scores_cache [ cache_key ] if @scores_cache . key? ( cache_key )
73+
74+ return sequence . length if depth == 0
75+
76+ total = current
77+ sequence . chars . each_with_index do |key , index |
78+ next_key = sequence [ index - 1 ]
79+ total += score ( moves ( next_key , key ) , depth - 1 )
80+ end
81+
82+ @scores_cache [ cache_key ] = total
83+ end
84+
85+ def moves ( current_key , next_key )
86+ cache_key = [ current_key , next_key ]
87+
88+ return @moves_cache [ cache_key ] if @moves_cache . key? ( cache_key )
89+
90+ # Pick the pad given the current and next key
91+ pad = ( KEYPAD . key? ( current_key ) && KEYPAD . key? ( next_key ) ) ? KEYPAD : NUMPAD
92+
93+ # puts "-" * 40
94+ # puts "current_key: #{current_key}, next_key: #{next_key}"
95+ # puts "#{KEYPAD.keys}"
96+ # puts "pad: #{pad}"
97+ # puts "pad[current_key]: #{pad[current_key]}, pad[next_key]: #{pad[next_key]}"
98+ # puts "-" * 40
99+
100+ # Get x,y distances to move
101+ distance = [ pad [ next_key ] [ 0 ] - pad [ current_key ] [ 0 ] , pad [ next_key ] [ 1 ] - pad [ current_key ] [ 1 ] ]
102+ # Map out key presses
103+ presses = xdir ( distance [ 0 ] ) * distance [ 0 ] . abs + ydir ( distance [ 1 ] ) * distance [ 1 ] . abs
104+
105+ if pad [ " " ] == [ pad [ next_key ] [ 0 ] , pad [ current_key ] [ 1 ] ]
106+ result = presses . reverse + 'A'
107+ elsif pad [ " " ] == [ pad [ current_key ] [ 0 ] , pad [ next_key ] [ 1 ] ]
108+ result = presses + 'A'
109+ else
110+ result = ( rand < 0.5 ? presses : presses . reverse ) + 'A'
111+ end
112+
113+ @moves_cache [ cache_key ] = result
114+ end
115+ end
116+ end
0 commit comments