--- Day 15: Lanternfish Sokoban ---

December 15

Listening to: N.U.D.E. | Liquid DnB / Jungle Mix


For those that have noticed - Yes I've been listening to the same thing for like 4 problems in a row now. It's such a long mix and I haven't even gone through it once, in part because I've only been listening while doing Advent of Code. It's good for focusing oddly enough.


Time for l̴͖͍̃̕ā̵̤͛͜ń̴̩͇t̶̺̑e̷̗̞̙͗r̵̯̟̩̂͐n̶̛͙̗͘f̵̨̺̟́i̸̳͐͜s̴͍̿h̶̜͝.

Well okay not really. Narratively it's lanternfish, but the problem itself is far different. It's more reminiscent of Sokoban, fun little box-pushing puzzles.

Anyway, I think a recusive solution might work for this? Writing a function for each direction that recursively tries to push boxes in each of the given directions:

  • If the space is empty (.), it returns success to allow the previous move to work
  • If the space is a wall (#), it returns failure to disallow moving
  • If the space is anythign else, it recursively keeps going in that direction
foreach (var move in moves)
{
    switch (move)
    {
        case '<': if (Move(warehouse, x, y, 0, -1)) y -= 1; break;
        case '>': if (Move(warehouse, x, y, 0, 1)) y += 1; break;
        case '^': if (Move(warehouse, x, y, -1, 0)) x -= 1; break;
        case 'v': if (Move(warehouse, x, y, 1, 0)) x += 1; break;
    }
}
private bool Move(List<List<char>> warehouse, int x, int y, int dx, int dy)
{
    var nx = x + dx;
    var ny = y + dy;

    bool canMove = warehouse[nx][ny] switch
    {
        '#' => false,
        '.' => true,
        _ => Move(warehouse, nx, ny, dx, dy)
    };

    if (canMove)
    {
        warehouse[nx][ny] = warehouse[x][y];
        warehouse[x][y] = '.';
    }
    return canMove;
}

Boxes are fun!


Mr. President, a second robot has hit the warehouse.

But no, really, this seems like an amusing second part. Everything is twice as wide - a completely different puzzle from the first puzzle. Aside from modifying the grid for the warehouse, there really only needs to be one modification:

  • If moving a box ([ or ]) either up or down (dx != 0), then it's a recursive check for both sides of the box

I wasn't sure before if doing it recursively would have been the right move, but it turns out to have been perfect! The only real significant change I need to do is split the Move() function to a CanMove() and a DoMove(), otherwise it can get really tricky.

private bool CanMove(List<List<char>> warehouse, int x, int y, int dx, int dy)
{
    var nx = x + dx;
    var ny = y + dy;

    return warehouse[nx][ny] switch
    {
        '#' => false,
        '.' => true,
        '[' => dx == 0 ? CanMove(warehouse, nx, ny, dx, dy) :
            CanMove(warehouse, nx, ny, dx, dy) && CanMove(warehouse, nx, ny + 1, dx, dy),
        ']' => dx == 0 ? CanMove(warehouse, nx, ny, dx, dy) :
            CanMove(warehouse, nx, ny, dx, dy) && CanMove(warehouse, nx, ny - 1, dx, dy),
        _ => CanMove(warehouse, nx, ny, dx, dy)
    };
}

private void DoMove(List<List<char>> warehouse, int x, int y, int dx, int dy)
{
    var nx = x + dx;
    var ny = y + dy;

    if (dx != 0 && warehouse[nx][ny] == '[') DoMove(warehouse, nx, ny + 1, dx, dy);
    if (dx != 0 && warehouse[nx][ny] == ']') DoMove(warehouse, nx, ny - 1, dx, dy);
    if (warehouse[nx][ny] != '.') DoMove(warehouse, nx, ny, dx, dy);
    Utils.Swap2D(warehouse, x, y, nx, ny);
}

That worked easily, despite taking a bit of tweaking for the up/down movement.


Time taken: 90 minutes

My solutions for today's puzzles