Skip to content

Commit 668008e

Browse files
committed
feat(day18): alternative aproach (defunct)
1 parent a4b3761 commit 668008e

File tree

1 file changed

+338
-0
lines changed

1 file changed

+338
-0
lines changed

day18/test.ts

Lines changed: 338 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
type Position = {
2+
x: number
3+
y: number
4+
}
5+
6+
type POI = {
7+
letter: string
8+
pos: Position
9+
}
10+
11+
type Maze = {
12+
maze: string
13+
width: number
14+
}
15+
16+
type Location = {
17+
pos: Position
18+
pois: POI[]
19+
distance: number
20+
status: 'Start' | 'Valid' | 'Blocked' | 'Target'
21+
}
22+
23+
type Connection = {
24+
from: POI
25+
to?: POI
26+
path?: Location
27+
}
28+
29+
export enum Tile {
30+
START = '@',
31+
PATH = '.',
32+
WALL = '#',
33+
}
34+
35+
export enum DIRECTION {
36+
UP,
37+
DOWN,
38+
LEFT,
39+
RIGHT,
40+
}
41+
42+
const isSafe = (map: Maze, visited: boolean[], pos: Position) => {
43+
const p = pos.y * map.width + pos.x
44+
if (map.maze[p] === undefined) return false
45+
if (map.maze[p] === Tile.WALL) return false
46+
if (visited[p]) return false
47+
return true
48+
}
49+
50+
const status = (map: Maze, visited: boolean[], location: Position) => {
51+
if (!isSafe(map, visited, location)) return 'Blocked'
52+
return 'Valid'
53+
}
54+
55+
const createLocation = (map: Maze, visited: boolean[], location: Location) => (
56+
pos: Position,
57+
): Location => {
58+
const p = pos.y * map.width + pos.x
59+
const t = map.maze[p]
60+
const pois = [...location.pois]
61+
if (/[A-Z]/i.test(t)) {
62+
// Door/Key?
63+
pois.push({
64+
letter: t,
65+
pos,
66+
})
67+
}
68+
return {
69+
pos: pos,
70+
pois,
71+
status: status(map, visited, pos),
72+
distance: location.distance + 1,
73+
}
74+
}
75+
76+
const exploreInDirection = (
77+
map: Maze,
78+
visited: boolean[],
79+
target: POI,
80+
location: Location,
81+
direction: DIRECTION,
82+
): Location => {
83+
let newLocation: Location
84+
const cl = createLocation(map, visited, location)
85+
switch (direction) {
86+
case DIRECTION.UP:
87+
newLocation = cl({ x: location.pos.x, y: location.pos.y - 1 } as Position)
88+
break
89+
case DIRECTION.DOWN:
90+
newLocation = cl({ x: location.pos.x, y: location.pos.y + 1 } as Position)
91+
break
92+
case DIRECTION.LEFT:
93+
newLocation = cl({ x: location.pos.x - 1, y: location.pos.y } as Position)
94+
break
95+
case DIRECTION.RIGHT:
96+
newLocation = cl({ x: location.pos.x + 1, y: location.pos.y } as Position)
97+
break
98+
}
99+
100+
if (newLocation.status === 'Valid') {
101+
const p = newLocation.pos.y * map.width + newLocation.pos.x
102+
if (map.maze[p] === target.letter) {
103+
return {
104+
...newLocation,
105+
status: 'Target',
106+
}
107+
}
108+
visited[newLocation.pos.y * map.width + newLocation.pos.x] = true
109+
}
110+
111+
return newLocation
112+
}
113+
114+
const equals = (a: Position, b: Position) => a.x === b.x && a.y === b.y
115+
116+
type POIPath = {
117+
poi: POI
118+
path: Location[]
119+
}
120+
121+
const shortestPathToFinalKey = (
122+
map: Maze,
123+
start: POIPath,
124+
target: POI,
125+
connections: Connection[],
126+
collectedKeys: string[] = [],
127+
): POIPath | undefined => {
128+
if (start.poi.letter === target.letter) {
129+
return start
130+
}
131+
132+
const reachableKeys = connections
133+
.filter(({ from }) => equals(from.pos, start.poi.pos)) // reachable from here
134+
.filter(({ to }) => /[a-z]/.test(to?.letter ?? '')) // is a key
135+
.filter(({ to }) => !collectedKeys.includes((to as POI).letter)) // not already collected
136+
.filter(({ path }) => {
137+
const doorsInPath = path?.pois.filter(({ letter }) =>
138+
/[A-Z]/.test(letter),
139+
)
140+
const lockedDoors = doorsInPath?.filter(
141+
({ letter }) => !collectedKeys.includes(letter.toLowerCase()),
142+
)
143+
return lockedDoors?.length === 0 ?? false
144+
})
145+
.sort(
146+
({ path: p1 }, { path: p2 }) =>
147+
(p1?.distance ?? Number.MAX_SAFE_INTEGER) -
148+
(p2?.distance ?? Number.MAX_SAFE_INTEGER),
149+
)
150+
151+
const nextKey = reachableKeys.shift()
152+
if (nextKey) {
153+
return shortestPathToFinalKey(
154+
map,
155+
{
156+
poi: nextKey.to as POI,
157+
path: [...start.path, nextKey.path as Location],
158+
},
159+
target,
160+
connections,
161+
[...collectedKeys, nextKey?.to?.letter as string],
162+
)
163+
}
164+
165+
return undefined
166+
}
167+
168+
const findShortestPath = (
169+
map: Maze,
170+
start: POI,
171+
target: POI,
172+
): Location | undefined => {
173+
const visited = [] as boolean[]
174+
175+
const queue = [
176+
{
177+
pois: [],
178+
pos: start.pos,
179+
status: 'Start',
180+
distance: 0,
181+
},
182+
] as Location[]
183+
184+
while (queue.length > 0) {
185+
const location = queue.shift() as Location
186+
187+
// Up
188+
const up = exploreInDirection(map, visited, target, location, DIRECTION.UP)
189+
if (up.status === 'Target') {
190+
return up
191+
} else if (up.status === 'Valid') {
192+
queue.push(up)
193+
}
194+
195+
// Right
196+
const right = exploreInDirection(
197+
map,
198+
visited,
199+
target,
200+
location,
201+
DIRECTION.RIGHT,
202+
)
203+
if (right.status === 'Target') {
204+
return right
205+
} else if (right.status === 'Valid') {
206+
queue.push(right)
207+
}
208+
209+
// Down
210+
const down = exploreInDirection(
211+
map,
212+
visited,
213+
target,
214+
location,
215+
DIRECTION.DOWN,
216+
)
217+
if (down.status === 'Target') {
218+
return down
219+
} else if (down.status === 'Valid') {
220+
queue.push(down)
221+
}
222+
223+
// Left
224+
const left = exploreInDirection(
225+
map,
226+
visited,
227+
target,
228+
location,
229+
DIRECTION.LEFT,
230+
)
231+
if (left.status === 'Target') {
232+
return left
233+
} else if (left.status === 'Valid') {
234+
queue.push(left)
235+
}
236+
}
237+
238+
return undefined
239+
}
240+
241+
const solveDoorMaze2 = (maze: string) => {
242+
const width = maze.trim().indexOf('\n')
243+
const mazeString = maze
244+
.split('\n')
245+
.map(s => s.trim())
246+
.join('')
247+
const map = { maze: mazeString, width }
248+
249+
// Find start
250+
const startPos = mazeString.indexOf('@')
251+
const start: POI = {
252+
letter: '@',
253+
pos: {
254+
x: startPos % width,
255+
y: Math.floor(startPos / width),
256+
},
257+
}
258+
259+
// Find all keys
260+
const keys = mazeString.split('').reduce((keys, char, index) => {
261+
if (/[a-z]/.test(char)) {
262+
keys.push({
263+
letter: char,
264+
pos: {
265+
x: index % width,
266+
y: Math.floor(index / width),
267+
},
268+
})
269+
}
270+
return keys
271+
}, [] as POI[])
272+
273+
// Calculate distances between start and keys
274+
const startToKeys = keys
275+
.filter(({ letter }) => /[a-z]/.test(letter))
276+
.map(
277+
key =>
278+
({
279+
from: start,
280+
to: key,
281+
path: findShortestPath(map, start, key),
282+
} as Connection),
283+
)
284+
285+
// Calculate distances between keys
286+
const keysToKeys = keys
287+
.map(key =>
288+
keys
289+
.filter(otherKey => otherKey !== key)
290+
.map(
291+
otherKey =>
292+
({
293+
from: key,
294+
to: otherKey,
295+
path: findShortestPath(map, key, otherKey),
296+
} as Connection),
297+
),
298+
)
299+
.flat()
300+
301+
const doorLetters = mazeString.split('').filter(s => /[A-Z]/.test(s))
302+
303+
const finalKey = keys
304+
.filter(({ letter }) => /[a-z]/.test(letter))
305+
.find(({ letter }) => !doorLetters.includes(letter)) as POI
306+
307+
const res = shortestPathToFinalKey(
308+
map,
309+
{
310+
poi: start,
311+
path: [],
312+
},
313+
finalKey,
314+
[...startToKeys, ...keysToKeys],
315+
)
316+
317+
console.dir(res, { depth: 10 })
318+
console.log(res?.path.reduce((total, p) => total + p.distance, 0))
319+
}
320+
321+
/*
322+
solveDoorMaze2(
323+
`
324+
#########
325+
#b.A.@.a#
326+
#########
327+
`)
328+
*/
329+
330+
solveDoorMaze2(
331+
`
332+
########################
333+
#f.D.E.e.C.b.A.@.a.B.c.#
334+
######################.#
335+
#d.....................#
336+
########################
337+
`,
338+
)

0 commit comments

Comments
 (0)