1+ import { delay , Console , Vector2D , PixelMap } from "../../utility.mjs" ;
2+
3+ const weakenedColorIndex = 1 ;
4+ const weakenedColor = "#999999" ;
5+ const infectedColorIndex = 2 ;
6+ const infectedColor = "#ff0000" ;
7+ const flaggedColorIndex = 3 ;
8+ const flaggedColor = "#00aa00" ;
9+
10+ export default class {
11+ /**
12+ * @param {Console } solConsole Solution console.
13+ * @param {HTMLElement } visContainer Visualization container.
14+ */
15+ constructor ( solConsole , visContainer ) {
16+ this . isSolving = false ;
17+ this . isStopping = false ;
18+ this . solConsole = typeof solConsole !== "undefined" ? solConsole : new Console ( ) ;
19+ this . visContainer = visContainer ;
20+ }
21+
22+ /**
23+ * Parses the puzzle input.
24+ * @param {string } input Puzzle input.
25+ * @returns {Vector2D[] } Infected nodes.
26+ */
27+ parse ( input ) {
28+ let consoleLine = this . solConsole . addLine ( "Parsing..." ) ;
29+
30+ let letters = { } ;
31+ let map = input . split ( / \r ? \n / ) . filter ( e => e != "" ) . map ( ( line , index , lines ) => {
32+ if ( line . length != lines [ 0 ] . length )
33+ throw new Error ( `Invalid length of line ${ index + 1 } ` ) ;
34+ if ( ! / ^ [ # \. ] + $ / . test ( line ) )
35+ throw new Error ( `Invalid data in line ${ index + 1 } ` ) ;
36+ return line . split ( "" ) ;
37+ } ) ;
38+
39+ let mapWidth = map [ 0 ] . length , mapHeight = map . length ;
40+
41+ if ( mapWidth % 2 == 0 || mapHeight % 2 == 0 )
42+ throw new Error ( "Invalid map size" ) ;
43+
44+ let infectedNodes = [ ] ;
45+ for ( let x = 0 ; x < mapHeight ; x ++ ) {
46+ for ( let y = 0 ; y < mapWidth ; y ++ ) {
47+ if ( map [ y ] [ x ] != "." )
48+ infectedNodes . push ( new Vector2D ( x - Math . floor ( mapWidth / 2 ) , y - Math . floor ( mapHeight / 2 ) ) ) ;
49+ }
50+ }
51+
52+ consoleLine . innerHTML += " done." ;
53+ return infectedNodes ;
54+ }
55+
56+ /**
57+ * Finds the number of infection bursts.
58+ * @param {number } part Puzzle part.
59+ * @param {string } input Puzzle input.
60+ * @param {boolean } visualization Enable visualization.
61+ * @returns {number } Number of infection bursts.
62+ */
63+ async solve ( part , input , visualization ) {
64+ try {
65+ this . isSolving = true ;
66+
67+ let infectedNodes = this . parse ( input ) ;
68+ let numberOfSteps = part == 1 ? 10000 : 10000000 ;
69+
70+ let directions = [ new Vector2D ( 0 , - 1 ) , new Vector2D ( 1 , 0 ) , new Vector2D ( 0 , 1 ) , new Vector2D ( - 1 , 0 ) ] ;
71+ let position = new Vector2D ( 0 , 0 ) , directionIndex = 0 ;
72+ // Nodes: 1 - weakened, 2 - infected, 3 - flagged
73+ let nodeMap = infectedNodes . reduce ( ( acc , e ) => acc . set ( e . y * numberOfSteps * 2 + e . x , 2 ) , new Map ( ) ) ;
74+ let numberOfInfectionBursts = 0 ;
75+
76+ let minCoordinates = new Vector2D ( Math . min ( 0 , ...infectedNodes . map ( e => e . x ) ) , Math . min ( 0 , ...infectedNodes . map ( e => e . y ) ) ) ;
77+ let maxCoordinates = new Vector2D ( Math . max ( 0 , ...infectedNodes . map ( e => e . x ) ) , Math . max ( 0 , ...infectedNodes . map ( e => e . y ) ) ) ;
78+ infectedNodes . forEach ( e => minCoordinates )
79+ for ( let i = 0 ; i < numberOfSteps ; i ++ ) {
80+ let nodeMapKey = position . y * numberOfSteps * 2 + position . x ;
81+ let nodeValue = nodeMap . get ( nodeMapKey ) ;
82+ if ( nodeValue == undefined ) {
83+ if ( part == 1 ) {
84+ nodeMap . set ( nodeMapKey , 2 ) ;
85+ numberOfInfectionBursts ++ ;
86+ }
87+ else
88+ nodeMap . set ( nodeMapKey , 1 ) ;
89+ directionIndex = ( directionIndex + 3 ) % directions . length ;
90+ }
91+ else if ( nodeValue == 1 ) {
92+ nodeMap . set ( nodeMapKey , 2 ) ;
93+ numberOfInfectionBursts ++ ;
94+ }
95+ else if ( nodeValue == 2 ) {
96+ if ( part == 1 )
97+ nodeMap . delete ( nodeMapKey ) ;
98+ else
99+ nodeMap . set ( nodeMapKey , 3 ) ;
100+ directionIndex = ( directionIndex + 1 ) % directions . length ;
101+ }
102+ else if ( nodeValue == 3 ) {
103+ nodeMap . delete ( nodeMapKey ) ;
104+ directionIndex = ( directionIndex + 2 ) % directions . length ;
105+ }
106+ position . add ( directions [ directionIndex ] ) ;
107+
108+ if ( visualization ) {
109+ minCoordinates . x = Math . min ( minCoordinates . x , position . x ) ;
110+ minCoordinates . y = Math . min ( minCoordinates . y , position . y ) ;
111+ maxCoordinates . x = Math . max ( maxCoordinates . x , position . x ) ;
112+ maxCoordinates . y = Math . max ( maxCoordinates . y , position . y ) ;
113+ }
114+ }
115+
116+ if ( visualization ) {
117+ let numberOfInfectionBursts = 0 ;
118+ let mapWidth = maxCoordinates . x - minCoordinates . x + 1 , mapHeight = maxCoordinates . y - minCoordinates . y + 1 ;
119+ this . solConsole . addLine ( `Number of steps: ${ numberOfSteps } .` ) ;
120+ let solConsoleLine1 = this . solConsole . addLine ( ) ;
121+ let solConsoleLine2 = this . solConsole . addLine ( ) ;
122+ let pixelMap = new PixelMap ( mapWidth , mapHeight ) ;
123+ this . visContainer . append ( pixelMap . container ) ;
124+ pixelMap . palette [ weakenedColorIndex ] = weakenedColor ;
125+ pixelMap . palette [ infectedColorIndex ] = infectedColor ;
126+ pixelMap . palette [ flaggedColorIndex ] = flaggedColor ;
127+ for ( let infectedNode of infectedNodes )
128+ pixelMap . drawPixel ( infectedNode . x - minCoordinates . x , infectedNode . y - minCoordinates . y , infectedColorIndex ) ;
129+ let position = new Vector2D ( - minCoordinates . x , - minCoordinates . y ) ;
130+ directionIndex = 0 ;
131+
132+ for ( let i = 0 ; i < numberOfSteps ; i ++ ) {
133+ if ( this . isStopping )
134+ return ;
135+
136+ let x = position . x , y = position . y ;
137+ let pixel = pixelMap . image [ y ] [ x ] ;
138+ if ( pixel == 0 ) {
139+ if ( part == 1 ) {
140+ pixelMap . drawPixel ( x , y , infectedColorIndex ) ;
141+ numberOfInfectionBursts ++ ;
142+ }
143+ else
144+ pixelMap . drawPixel ( x , y , weakenedColorIndex ) ;
145+ directionIndex = ( directionIndex + 3 ) % directions . length ;
146+ }
147+ else if ( pixel == weakenedColorIndex ) {
148+ pixelMap . drawPixel ( x , y , infectedColorIndex ) ;
149+ numberOfInfectionBursts ++ ;
150+ }
151+ else if ( pixel == infectedColorIndex ) {
152+ pixelMap . drawPixel ( x , y , part == 1 ? 0 : flaggedColorIndex ) ;
153+ directionIndex = ( directionIndex + 1 ) % directions . length ;
154+ }
155+ else if ( pixel == flaggedColorIndex ) {
156+ pixelMap . drawPixel ( x , y , 0 ) ;
157+ directionIndex = ( directionIndex + 2 ) % directions . length ;
158+ }
159+ position . add ( directions [ directionIndex ] ) ;
160+
161+ if ( visualization ) {
162+ minCoordinates . x = Math . min ( minCoordinates . x , position . x ) ;
163+ minCoordinates . y = Math . min ( minCoordinates . y , position . y ) ;
164+ maxCoordinates . x = Math . max ( maxCoordinates . x , position . x ) ;
165+ maxCoordinates . y = Math . max ( maxCoordinates . y , position . y ) ;
166+ }
167+
168+ if ( ( i + 1 ) % ( numberOfSteps / 1000 ) == 0 ) {
169+ solConsoleLine1 . innerHTML = `Step: ${ i + 1 } .` ;
170+ solConsoleLine2 . innerHTML = `Infection bursts: ${ numberOfInfectionBursts } .` ;
171+ await delay ( 1 ) ;
172+ }
173+ }
174+ }
175+
176+ return numberOfInfectionBursts ;
177+ }
178+
179+ finally {
180+ this . isSolving = false ;
181+ }
182+ }
183+
184+ /**
185+ * Stops solving the puzzle.
186+ */
187+ async stopSolving ( ) {
188+ this . isStopping = true ;
189+ while ( this . isSolving )
190+ await ( delay ( 10 ) ) ;
191+ this . isStopping = false ;
192+ }
193+ }
0 commit comments