--- /dev/null
+Split←{
+ blocknums ← +`𝕨
+ # Set entries in blocknums to -1 when they correspond with delimiters.
+ blocknums ↩ (¯1¨)⌾(𝕨⊸/) blocknums
+ blocknums⊔𝕩
+}
+
+_and_and_←{𝕨𝔽◶⟨0,𝕘⟩𝕩}
+_or_or_ ←{𝕨𝔽◶⟨𝕘,1⟩𝕩}
+
+Prep←{𝕊 lines:
+ {
+ fields←(⊑∘∊⟜" ->"¨𝕩) Split 𝕩 # Ewww
+ fields↩(0=4|↕≠fields)/fields
+ {
+ •ParseFloat¨(','=𝕩)Split𝕩
+ }¨fields
+ }¨lines
+}
+
+RectIndices←{
+ i←>𝕨⋈𝕩
+ (⌊˝i)⊸+¨⥊↕(1+⌈˝i)-(⌊˝i)
+}
+
+Plot←{
+ # Assume all co-ordinates are positive.
+ ⟨max_x, max_y⟩ ← (⟨1,3⟩+⌈˝>∾𝕩)
+ max_x ↩ max_x ⌈ 500+max_y
+ map←⟨max_x,max_y⟩⥊0
+ map ↩ 1¨⌾(¯1⊸⊏˘) map # Solid floor
+ {𝕊⟨from,to⟩:
+ map ↩ 1¨⌾((from RectIndices to)⊸⊑) map
+ }˘∾2↕¨𝕩
+ map
+}
+
+Main←{𝕊map:
+ grains←0
+ Settle←{
+ map 1⌾(𝕩⊸⊑) ↩
+ grains +↩ 1
+ 1
+ }
+ # Return 0 if the sand falls forever; 1 once a path is full.
+ OnMap←{ (∧´𝕩≥⟨0,0⟩)∧(∧´𝕩<≢map) } # 0 if fallen off the map.
+ Solid←{ 𝕩⊑map } # Hit something solid.
+ FilledDown ←{Pour ⟨0,1⟩+𝕩}
+ FilledLeft ←{Pour ⟨¯1,1⟩+𝕩}
+ FilledRight←{Pour ⟨1,1⟩+𝕩}
+ Pour← OnMap _and_and_ (Solid _or_or_ (FilledDown _and_and_ FilledLeft _and_and_ FilledRight _and_and_ Settle))
+ Pour ⟨500,0⟩
+ #•Show (400↓map)⊏".#"
+ grains
+}
+
+•Show Main Plot Prep •file.Lines ⊑•args