--- Day 22: Tetris, but 3D ---

December 22

Listening to: Video Game LoFi: Pokémon


Return the Slab...

... or suffer my curse


Or I guess, multiple slabs? It's kinda like tetris, but 3d. This is going to be a pain to try and visualize this if I try and print it out, but I'm sure I can manage it. But otherwise, the falling algorithm should be simple: starting from where it's "spawned" from in the input, check to see if it can be lowered; if so, do so; repeat until it is resting down on other bricks or the ground.

Re-reading the writeup, as well as confirming with a quick scan of the input, all of the bricks only go along one dimension. I can likely simplify things if I just have some start coordinate, axis, and length. It's likely best to have it like this in case part 2 requires rotation, which hopefully will keep things simpler.

struct Coord { int x, y, z; };
struct Brick {
    int id;
    vector<Coord> spaces;
};

vector<Brick> bricks;
int grid[MAX_X][MAX_Y][MAX_Z];

newBrick.spaces = vector<Coord>();
for (int x = start.x; x <= end.x; x++) {
    for (int y = start.y; y <= end.y; y++) {
        for (int z = start.z; z <= end.z; z++) {
            newBrick.spaces.push_back(Coord{x, y, z});
            grid[x][y][z] = true;
        }
    }
}

One thing I did notice is that the inputs are not sorted top to bottom, so one thing I'll have to do is loop through this multiple times. The simpler way is to just loop through the whole list until a full iterations doesn't result in a piece falling.

Now that that's done... it looks like the hard part is predicting if a piece can fall safely. It seems like the best way to figure that out is, for any given brick:

  • Check all bricks candidate is supporting
  • If any supported brick is ONLY supported by the current brick, don't destroy
// Check for disintegration
int count = 0;
for (auto brick: bricks)
{
    bool canDestroy = true;
    auto supportedList = supporting(brick);

    for (auto supported : supportedList)
    {
        auto sby = supportedBy(bricks[supported]);
        if (sby.size() == 1)
        {
            canDestroy = false;
            break;
        }
    }

    if (canDestroy) count++;
}

And that seems to for the sample, but not the full input... hmm...

... after trowling reddit for some hints, I saw a sample input that my code gave the wrong answer for - it seems that if a block is touching another block multiple times, it's counting each segment, not the whole block! Those supporting() and supportedBy functions are returning vectors; changing those to sets fixes the issue!

Note: Check your conditionals thoroughly. I also had a == that should have been a != in a couple of places that kept me scratching my head for embarassingly long.


Surprisingly for me, part 2 seems like an easy extention of part 1 that I should easily be able to do. I need to find out how many other bricks would fall for any given brick. I can just take the (inverted) list from part 1, and "climb" up the tree to see what's soely supported by that brick. If any others would be removed, keep moving up, checking to see it's remaining supported bricks.

uint64_t totalFall = 0;
for (int destroyId : shouldDestroy)
{
    set<int> moved;
    queue<int> toCheck;

    moved.insert(destroyId);
    for (int supported : supporting(bricks[destroyId])) toCheck.push(supported);

    while (!toCheck.empty())
    {
        int checkId = toCheck.front(); toCheck.pop();
        
        auto belowToCheck = supportedBy(bricks[checkId]);
        int stillSupporting = belowToCheck.size();
        for (int checkBelow : belowToCheck)
        {
            if (moved.find(checkBelow) != moved.end()) stillSupporting--;
        }

        if (stillSupporting == 0)
        {
            moved.insert(checkId);
            for (int s : supporting(bricks[checkId])) toCheck.push(s);
        }
    }

    totalFall += moved.size() - 1;
}

Time taken: 2 hours 40 minutes

My solutions for today's puzzles