DEV Community

JoeStrout
JoeStrout

Posted on • Updated on

Advent of Code (in MiniScript), Day 14

Welcome again to my series of Advent of Code solutions in MiniScript! 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

Since I do my work in Mini Micro, I have a 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
Enter fullscreen mode Exit fullscreen mode

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 (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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Final state of Part 2 challenge, with sand piled up in a big pyramid shape

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 and give it a try?

Top comments (0)