1+ import { delay , Console , Vector2D } from "../../utility.mjs" ;
2+
3+ export default class {
4+ /**
5+ * @param {Console } solConsole Solution console.
6+ * @param {HTMLElement } visContainer Visualization container.
7+ */
8+ constructor ( solConsole , visContainer ) {
9+ this . isSolving = false ;
10+ this . isStopping = false ;
11+ this . solConsole = typeof solConsole !== "undefined" ? solConsole : new Console ( ) ;
12+ this . visContainer = visContainer ;
13+ }
14+
15+ /**
16+ * Parses the puzzle input.
17+ * @param {string } input Puzzle input.
18+ * @returns {number } Square number.
19+ */
20+ parse ( input ) {
21+ let consoleLine = this . solConsole . addLine ( "Parsing..." ) ;
22+
23+ input = input . trim ( ) ;
24+ if ( ! / ^ \d + $ / . test ( input ) )
25+ throw new Error ( "Invalid input data" )
26+
27+ let squareNumber = parseInt ( input ) ;
28+
29+ consoleLine . innerHTML += " done." ;
30+ return squareNumber ;
31+ }
32+
33+
34+ /**
35+ * Finds the Manhattan distance from the specified square to the center of the spiral (part 1) or the first square value that is larger than the specified value (part 2).
36+ * @param {number } part Puzzle part.
37+ * @param {string } input Puzzle input.
38+ * @param {boolean } visualization Enable visualization.
39+ * @returns {number } Manhattan distance from the specified square to the center of the spiral (part 1) or the first square value that is larger than the specified value (part 2).
40+ */
41+ async solve ( part , input , visualization ) {
42+ try {
43+ this . isSolving = true ;
44+
45+ let puzzleInput = this . parse ( input ) ;
46+
47+ let visConsole = new Console ( ) ;
48+ if ( visualization )
49+ this . visContainer . append ( visConsole . container ) ;
50+
51+ if ( part == 1 ) {
52+ let squareCoordinates = this . squareNumberToCoordinates ( puzzleInput ) ;
53+ if ( visualization )
54+ visConsole . addLine ( `Square ${ puzzleInput } coordinates relative to the spiral center: (${ squareCoordinates . x } , ${ squareCoordinates . y } ).` ) ;
55+ return Math . abs ( squareCoordinates . x ) + Math . abs ( squareCoordinates . y ) ;
56+ }
57+ else {
58+ let value = 0 ;
59+ let valueMap = new Map ( ) ;
60+ for ( let i = 1 ; value <= puzzleInput ; i ++ ) {
61+ let coordinates = this . squareNumberToCoordinates ( i ) ;
62+
63+ if ( i == 1 )
64+ value = 1 ;
65+ else {
66+ let neighborsCoordinates = [ new Vector2D ( coordinates . x - 1 , coordinates . y - 1 ) , new Vector2D ( coordinates . x , coordinates . y - 1 ) , new Vector2D ( coordinates . x + 1 , coordinates . y - 1 ) ,
67+ new Vector2D ( coordinates . x - 1 , coordinates . y ) , new Vector2D ( coordinates . x , coordinates . y ) , new Vector2D ( coordinates . x + 1 , coordinates . y ) ,
68+ new Vector2D ( coordinates . x - 1 , coordinates . y + 1 ) , new Vector2D ( coordinates . x , coordinates . y + 1 ) , new Vector2D ( coordinates . x + 1 , coordinates . y + 1 ) ] ;
69+ value = 0 ;
70+ for ( let neighborCoordinates of neighborsCoordinates ) {
71+ let neighborValue = valueMap . get ( `${ neighborCoordinates . x } |${ neighborCoordinates . y } ` ) ;
72+ if ( neighborValue != undefined )
73+ value += neighborValue ;
74+ }
75+ }
76+
77+ valueMap . set ( `${ coordinates . x } |${ coordinates . y } ` , value ) ;
78+
79+ if ( visualization ) {
80+ visConsole . addLine ( `Square ${ i } : <span${ value > puzzleInput ? " class='highlighted'" : "" } >${ value } </span>${ value > puzzleInput ? " > " + puzzleInput : "" } .` )
81+ visConsole . container . scrollTop = visConsole . lines [ visConsole . lines . length - 1 ] . offsetTop - visConsole . container . offsetHeight / 2 ;
82+ }
83+ }
84+ return value ;
85+ }
86+ }
87+
88+ finally {
89+ this . isSolving = false ;
90+ }
91+ }
92+
93+ /**
94+ * Calculates the coordinates of the specified square relative to the spiral center.
95+ * @param {number } squareNumber Square number.
96+ * @returns {Vector2D } Square coordinates relative to the spiral center.
97+ */
98+ squareNumberToCoordinates ( squareNumber ) {
99+ if ( squareNumber == 1 )
100+ return new Vector2D ( 0 , 0 ) ;
101+ // Numbers of squares in full turns are: 1, 8, 16, 24, 32...
102+ // Find the turn index from the square number using the arithmetic progression sum formula
103+ let turnIndex = Math . floor ( ( 1 + Math . sqrt ( 1 + 8 * ( squareNumber - 2 ) / 8 ) ) / 2 ) ;
104+ let squareIndexInTurn = ( squareNumber - 2 - turnIndex * ( turnIndex - 1 ) * 8 / 2 ) ;
105+ let sideIndex = Math . floor ( squareIndexInTurn / ( turnIndex * 2 ) ) ;
106+ let squareIndexOnTheSide = squareIndexInTurn % ( turnIndex * 2 ) ;
107+ if ( sideIndex == 0 )
108+ return new Vector2D ( turnIndex , turnIndex - 1 - squareIndexOnTheSide ) ;
109+ else if ( sideIndex == 1 )
110+ return new Vector2D ( turnIndex - 1 - squareIndexOnTheSide , - turnIndex ) ;
111+ else if ( sideIndex == 2 )
112+ return new Vector2D ( - turnIndex , squareIndexOnTheSide - ( turnIndex - 1 ) ) ;
113+ else
114+ return new Vector2D ( squareIndexOnTheSide - ( turnIndex - 1 ) , turnIndex ) ;
115+ }
116+
117+ /**
118+ * Stops solving the puzzle.
119+ */
120+ async stopSolving ( ) {
121+ this . isStopping = true ;
122+ while ( this . isSolving )
123+ await ( delay ( 10 ) ) ;
124+ this . isStopping = false ;
125+ }
126+ }
0 commit comments