103 lines
3.3 KiB
Smalltalk
103 lines
3.3 KiB
Smalltalk
Object subclass: Maze [
|
|
| grid start end bestScore
|
|
seen queue |
|
|
|
|
rows: r [ grid := Grid new rows: r. self findLandmarks ]
|
|
|
|
findLandmarks
|
|
[ grid allPosnsDo: [
|
|
:pos | | tile | tile := grid at: pos.
|
|
(tile = $S or: [tile = $E]) ifTrue: [
|
|
tile = $S ifTrue: [start := pos]
|
|
ifFalse: [end := pos].
|
|
grid at: pos put: $.. ]] ]
|
|
|
|
bestPath
|
|
[ "seen[pos][dir] -> {score . Set(previous-states ...)}"
|
|
seen := grid collect: [:i | LookupTable new].
|
|
"queue: score -> {pos . facing}"
|
|
queue := SortedCollection new.
|
|
|
|
self enqueuePos: start dir: Posn right withScore: 0 fromState: nil.
|
|
self enqueuePos: start dir: Posn up withScore: 1000 fromState: nil.
|
|
|
|
[ queue isEmpty ] whileFalse: [
|
|
| entry state |
|
|
entry := queue removeFirst.
|
|
entry value letArrayInBlock: [
|
|
:pos :dir |
|
|
self stepPathAt: pos dir: dir score: entry key ]].
|
|
|
|
bestScore := FloatD infinity.
|
|
(seen at: end) do: [:it | bestScore := bestScore min: (it at: 1)].
|
|
^bestScore ]
|
|
|
|
stepPathAt: oldPos dir: dir score: oldScore
|
|
[ | rots pos nextPos score |
|
|
rots := { dir rotateCw . dir rotateCcw }.
|
|
pos := oldPos.
|
|
score := oldScore.
|
|
|
|
[ nextPos := pos + dir.
|
|
(self isOpen: nextPos) ifFalse: [^nil].
|
|
score := score + 1.
|
|
self addPos: nextPos dir: dir withScore: score
|
|
fromState: { pos . dir }.
|
|
pos := nextPos.
|
|
|
|
rots do: [
|
|
:rot | | rotPos |
|
|
rotPos := pos + rot.
|
|
(self isOpen: rotPos) ifTrue: [
|
|
self enqueuePos: rotPos dir: rot
|
|
withScore: score + 1001
|
|
fromState: { pos . dir }.
|
|
]]
|
|
] repeat ]
|
|
|
|
addPos: pos dir: dir withScore: score fromState: oldState
|
|
[ | seenState oldScore cameFrom seenDirState |
|
|
seenDirState := seen at: pos.
|
|
seenState := seenDirState at: dir ifAbsent: [
|
|
seenDirState at: dir put: {FloatD infinity . Set new}
|
|
].
|
|
oldScore := seenState at: 1.
|
|
cameFrom := seenState at: 2.
|
|
|
|
score > oldScore ifTrue: [^false].
|
|
score < oldScore ifTrue: [
|
|
seenState at: 1 put: score.
|
|
cameFrom empty ].
|
|
oldState ifNotNil: [ cameFrom add: oldState ].
|
|
^score ~= oldScore ]
|
|
|
|
enqueuePos: pos dir: dir withScore: score fromState: oldState
|
|
[ | ok |
|
|
ok := self addPos: pos dir: dir withScore: score fromState: oldState.
|
|
ok ifTrue: [queue add: score -> {pos . dir}].
|
|
^ok ]
|
|
|
|
isOpen: pos [ ^(grid at: pos) = $. ]
|
|
printOn: st [ st << grid ]
|
|
|
|
bestPathCount
|
|
[ | bestPathSet |
|
|
bestPathSet := Set new.
|
|
(seen at: end) keysAndValuesDo: [
|
|
:dir :i | (i at: 1) = bestScore ifTrue: [
|
|
self traceBackFrom: end dir: dir into: bestPathSet]].
|
|
^bestPathSet size ]
|
|
|
|
traceBackFrom: pos dir: dir into: set
|
|
[ set add: pos.
|
|
grid at: pos put: $O.
|
|
(((seen at: pos) at: dir ifAbsent: [^nil]) at: 2) do: [
|
|
:nPos :nDir | self traceBackFrom: nPos dir: nDir into: set
|
|
] asSpreader ]
|
|
]
|
|
|
|
AOC input: [ Maze new rows: stdin toLines asArray ];
|
|
part1: [ :maze | maze bestPath ];
|
|
part2: [ :maze | maze bestPathCount ];
|
|
result: [ :maze :part | part value: maze ];
|
|
finish.
|