Advent of Code (in MiniScript), Day 14
Tags: miniscript, adventofcode, programming
Published: 2022-12-14 15:54:58

Welcome again to my [series](https://dev.to/search?q=JoeStrout%20%23adventofcode&sort_by=published_at&sort_direction=asc) of [Advent of Code](https://adventofcode.com/) solutions in [MiniScript](https://miniscript.org)!  [Day 14](https://adventofcode.com/2022/day/14) was a fun one — the first one where I actually used pixel graphics _during_ the challenge!

The input file is a series of polylines (i.e., lists of points connected by straight lines).  We are to draw (or imagine drawing) these lines in a grid, and then start dropping sand particles into the grid from a specific point.  Sand particles fall down until they hit something; then they fall down-left if they can, else down-right.  If they can do neither, then they stick and are added to the grid at that point.

![Particles of sand flowing into a series of obstacles](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vjrhbkilz1sf6943rwdz.gif)

Since I do my work in [Mini Micro](https://miniscript.org/MiniMicro/), I have a [PixelDisplay](https://miniscript.org/wiki/PixelDisplay) that is perfect for this sort of thing.  It can efficiently get and set pixel colors, and can be resized and scaled as needed, so why not?

The only small hitch was, coordinates in the challenge were top-down, i.e., Y=0 was at the top, and Y values increased as you go down.  Mini Micro uses a bottom-up coordinate system, as in mathematics.  I could have simply allowed the image to be drawn upside-down — after all, you submit a numeric answer, not the image itself — but that offends my sensibilities.  So, I simply subtract all the Y values from 600 (which both inverts them and puts the top near the top of the screen) as I load them from the file.

So I quickly banged out the following program.
```
if 1 then fname = "input.txt" else fname = "example.txt"
lines = file.readLines(fname)
if not lines[-1] then lines.pop

clear
bgColor = "#000000FF"; sandColor = "#CC9900FF"
gfx.clear bgColor
gfx.scale = 3; gfx.scrollX = 1024; gfx.scrollY = 1200

// draw obstacles
for line in lines
	parts = split(line, " -> ")
	prevx = null; prevy = null
	for part in parts
		x = part.split(",")[0].val
		y = 600 - part.split(",")[1].val
		if prevx != null then
			gfx.line prevx, prevy, x, y
		end if
		prevx = x
		prevy = y
	end for
end for

// return true if landed, false if fell into abyss
dropOneGrain = function
	x = 500; y = 600 - 0
	// fall until we can't fall any more, or go off the bottom
	while true
		if y <= 0 then return false  // fell into abyss
		if gfx.pixel(x, y-1) == bgColor then
			y = y - 1
			continue
		else if gfx.pixel(x-1, y-1) == bgColor then
			x = x - 1
			y = y - 1
			continue
		else if gfx.pixel(x+1, y-1) == bgColor then
			x = x + 1
			y = y - 1
			continue
		end if
		gfx.setPixel x, y, sandColor
		return true
	end while
end function

count = 0
while dropOneGrain
	count = count + 1
	if gfx.pixel(500, 600) != bgColor then break
end while

print "Count: " + count
```

The program is straightforward.  First, a loop over the input lines splits each line into parts, and then further splits each part into X and Y values, which we use to draw lines into [gfx](https://miniscript.org/wiki/Gfx) (the default PixelDisplay).  Then, I made a `dropOneGrain` function that starts a single grain of sand at 500,600 and proceeds in a loop, checking the pixels below and moving it down and left/right until it can go no further.

In the first part of the challenge, we stop dropping sand when any particle falls off the last obstacle and into the abyss.  I check for that as `y <= 0` in my `dropOneGrain` function, and return `false` in that case.

Then, the main loop at the bottom simply drops (and counts) grains of sand until one returns false, and prints the count.  The animation above shows what it looked like (though I have slowed it down here for dramatic effect).

This took a little under 10 minutes, and earned me **66th place** (and 35 points!) in the Part 1 competition.  Woot!

## Part B

In the second part of the challenge, we are told to imagine there is a floor 2 units below the lowest obstacle, and that sand should pile up on this floor as well.  This time, we keep dropping sand until it piles all the way up to the starting position (i.e. the point from which sand drops).

This was a very minor modification of the first program.  I added a `minY = 600` line at the top of the program, and then when reading in the data and drawing lines, I added

`if y < minY then minY = y`

So after reading in all the data, I know what the minimum Y is.  Then inside the `dropOnGrain` function, instead of returning `false` when `y <= 0`, I changed it to

```
	if y <= minY-1 then
		gfx.setPixel x, y, sandColor  // infinite floor
		return true
	end if
```

This causes sand to pile up on the invisible floor.

Finally, at the bottom of the `dropOneGrain` method, where we are about to place a new bit of sand, we now check whether there is already sand there.  That can only happen if sand has piled up all the way to the starting point.

```
	if gfx.pixel(x,y) == sandColor then return false
```

{% spoiler The rest of the code is unchanged, but here's the complete program if you want to see it. %}
```
if 1 then fname = "input.txt" else fname = "example.txt"
lines = file.readLines(fname)
if not lines[-1] then lines.pop

clear
bgColor = "#000000FF"; sandColor = "#CC9900FF"
gfx.clear bgColor
gfx.scale = 3; gfx.scrollX = 1024; gfx.scrollY = 1200
minY = 600
for line in lines
	parts = split(line, " -> ")
	prevx = null; prevy = null
	for part in parts
		x = part.split(",")[0].val
		y = 600 - part.split(",")[1].val
		if prevx != null then
			gfx.line prevx, prevy, x, y
		end if
		prevx = x
		prevy = y
		if y < minY then minY = y
	end for
end for

// return true if landed, false if fell into abyss
dropOneGrain = function
	x = 500; y = 600 - 0
	// fall until we can't fall any more, or go off the bottom
	while true
		if y <= minY-1 then
			gfx.setPixel x, y, sandColor  // infinite floor
			return true
		end if
		if gfx.pixel(x, y-1) == bgColor then
			y = y - 1
			continue
		else if gfx.pixel(x-1, y-1) == bgColor then
			x = x - 1
			y = y - 1
			continue
		else if gfx.pixel(x+1, y-1) == bgColor then
			x = x + 1
			y = y - 1
			continue
		end if
		if gfx.pixel(x,y) == sandColor then return false
		gfx.setPixel x, y, sandColor
		return true
	end while
end function

count = 0
while dropOneGrain
	count = count + 1
end while

print "Count: " + count
```
{% endspoiler %}

![Final state of Part 2 challenge, with sand piled up in a big pyramid shape](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4v5v1abte2cq4l6hpf5.png)

This took a little under 5 minutes, including the run, which took 51 seconds (it's a _lot_ of sand!).  That placed me at rank 119 overall.

## Final Thoughts

I'm very pleased with how MiniScript and Mini Micro performed on this task.  The code was easy to write, and watching it solve the problem visually was a treat.  Getting and setting individual pixels isn't the fastest operation on the system, and that 51 second run time on Part 2 probably hurt my ranking.  But on the whole I'm still quite satisfied.

If you've been following along at home, and have been waiting for a particularly fun exercise to jump in and try it yourself, I recommend this one!  The problem is fairly straightforward, and being able to see what it's doing graphically makes it easy and fun.  Why not [download Mini Micro](https://miniscript.org/MiniMicro/) and give it a try?