@@ -13,7 +13,6 @@ export type Location = {
1313export enum Tile {
1414 PATH = '.' ,
1515 WALL = '#' ,
16- PORTAL = '@' ,
1716}
1817
1918export enum DIRECTION {
@@ -97,19 +96,16 @@ const exploreInDirection = (
9796 visited [ newLocation . level ] [
9897 newLocation . pos . y * maze . width + newLocation . pos . x
9998 ] = true
99+
100100 // Is this a portal?
101101 const portal = portals . find ( ( { pos } ) => equals ( pos , newLocation . pos ) )
102102 if ( portal ) {
103+ // Find the other side of the portal
103104 const pair = portals . find (
104105 p => p !== portal && p . label === portal . label ,
105106 ) as Portal
107+
106108 if ( recursive ) {
107- // When you enter the maze, you are at the outermost level (0);
108- // when at the outermost level, only the outer labels AA and ZZ
109- // function (as the start and end, respectively); all other outer
110- // labeled tiles are effectively walls. At any other level,
111- // AA and ZZ count as walls, but the other outer labeled tiles
112- // bring you one level outward.
113109 if ( location . level === 0 ) {
114110 // Outermost level
115111 if ( portal . label === END ) {
@@ -125,23 +121,22 @@ const exploreInDirection = (
125121 ...newLocation ,
126122 status : 'Blocked' ,
127123 }
128- } else {
129- // Inner portal takes you one level deeper
130- if ( visited [ newLocation . level + 1 ] === undefined ) {
131- visited [ newLocation . level + 1 ] = [ ]
132- }
133- visited [ newLocation . level + 1 ] [
134- pair . pos . y * maze . width + pair . pos . x
135- ] = true
136- return {
137- pos : pair . pos ,
138- path : [ ...location . path , location . pos , portal . pos ] ,
139- status : 'Valid' ,
140- level : newLocation . level + 1 ,
141- }
124+ }
125+ // Inner portal takes you one level deeper
126+ if ( visited [ newLocation . level + 1 ] === undefined ) {
127+ visited [ newLocation . level + 1 ] = [ ]
128+ }
129+ visited [ newLocation . level + 1 ] [
130+ pair . pos . y * maze . width + pair . pos . x
131+ ] = true
132+ return {
133+ pos : pair . pos ,
134+ path : [ ...location . path , location . pos , portal . pos ] ,
135+ status : 'Valid' ,
136+ level : newLocation . level + 1 ,
142137 }
143138 } else {
144- // Other level
139+ // Other levels
145140 if ( [ START , END ] . includes ( portal . label ) ) {
146141 // Start and end are not accessible
147142 return {
@@ -160,20 +155,19 @@ const exploreInDirection = (
160155 status : 'Valid' ,
161156 level : newLocation . level - 1 ,
162157 }
163- } else {
164- // Inner portal takes you one level deeper
165- if ( visited [ newLocation . level + 1 ] === undefined ) {
166- visited [ newLocation . level + 1 ] = [ ]
167- }
168- visited [ newLocation . level + 1 ] [
169- pair . pos . y * maze . width + pair . pos . x
170- ] = true
171- return {
172- pos : pair . pos ,
173- path : [ ...location . path , location . pos , portal . pos ] ,
174- status : 'Valid' ,
175- level : newLocation . level + 1 ,
176- }
158+ }
159+ // Inner portal takes you one level deeper
160+ if ( visited [ newLocation . level + 1 ] === undefined ) {
161+ visited [ newLocation . level + 1 ] = [ ]
162+ }
163+ visited [ newLocation . level + 1 ] [
164+ pair . pos . y * maze . width + pair . pos . x
165+ ] = true
166+ return {
167+ pos : pair . pos ,
168+ path : [ ...location . path , location . pos , portal . pos ] ,
169+ status : 'Valid' ,
170+ level : newLocation . level + 1 ,
177171 }
178172 }
179173 } else {
@@ -203,19 +197,41 @@ export type MazeString = {
203197 width : number
204198}
205199
200+ /**
201+ * Solves the maze using a depth-first search.
202+ *
203+ * First all portals are detected, then the solver starts at the tile labeled
204+ * with AA, and tries to find a way to ZZ. Portals are used to jump between
205+ * tiles.
206+ *
207+ * In recursive mode, the recursive rules apply to portals:
208+ * When you enter the maze, you are at the outermost level (0); when at the
209+ * outermost level, only the outer labels AA and ZZ function (as the start and
210+ * end, respectively); all other outer labeled tiles are effectively walls. At
211+ * any other level AA and ZZ count as walls, but the other outer labeled tiles
212+ * bring you one level outward.
213+ */
206214export const transportingMazeSolver = (
207215 maze : string ,
208216 recursive = false ,
209217) : Location | undefined => {
218+ // Input is a string with maze rows separated by newlines,
219+ // we assume that all lines are even spaced
210220 const width = maze . indexOf ( '\n' )
221+ // In this solution we operate on one long string
211222 const mazeString : MazeString = {
212223 width,
213224 maze : maze . trimEnd ( ) . replace ( / \n / g, '' ) ,
214225 }
215- const portals = findPortals ( maze )
226+
227+ // Find all the portals in the maze
228+ const portals = findPortals ( mazeString )
229+
230+ // We need to track visited postions for every level, starting at level 0
216231 const visited = [ ] as Visited
217232 visited [ 0 ] = [ ]
218233
234+ // Find the start positing (the tile with the label 'AA')
219235 const startPos = portals . find ( ( { label } ) => label === START ) as Portal
220236 const queue = [
221237 {
@@ -227,6 +243,7 @@ export const transportingMazeSolver = (
227243 ] as Location [ ]
228244 visited [ 0 ] [ startPos . pos . y * width + startPos . pos . x ] = true
229245
246+ // Now explore all possible directions until all options are exhausted or the target is found
230247 while ( queue . length > 0 ) {
231248 const location = queue . shift ( ) as Location
232249 const e = exploreInDirection (
@@ -272,24 +289,3 @@ export const transportingMazeSolver = (
272289
273290 return undefined
274291}
275-
276- const split = ( s : string , length : number ) => {
277- const ret = [ ]
278- for ( let offset = 0 , strLen = s . length ; offset < strLen ; offset += length ) {
279- ret . push ( s . slice ( offset , length + offset ) )
280- }
281- return ret
282- }
283-
284- export const drawSolution = ( maze : string , finalLocation : Location ) => {
285- const width = maze . indexOf ( '\n' )
286- const mapAsString = maze . trimEnd ( ) . replace ( / \n / g, '' )
287- let solution = mapAsString
288- finalLocation . path . forEach ( p => {
289- solution =
290- solution . substr ( 0 , p . y * width + p . x ) +
291- '@' +
292- solution . substr ( p . y * width + p . x + 1 )
293- } )
294- console . log ( split ( solution , width ) . join ( '\n' ) )
295- }
0 commit comments