Day 6, part two - Brighter

Advent of Code 2015

Adrien Foucart

2026-03-08

[Back to index]

Puzzle: https://adventofcode.com/2015/day/6

My solution: https://codeberg.org/adfoucart/aocode15/src/branch/main/src/day6.c

In part 1, we had to toggle a grid of lights on and off. In the second part, the instructions are the same, but their meaning change. Instead of toggling, we increment — or decrement — the brightness of the lights.

Which means that the grid is now a grid of integers instead of a grid of booleans, and the meaning of the operations change. “Turn on” becomes “increment”, “turn off” becomes “decrement” and “toggle” becomes “increment by two”. The brightness cannot be negative.

If I was in Python, this would be a trivial change, but I’m not in Python. This is the second time that I have to consider trying to make a “generic” version of a class: my str_list_t is mostly redundant with my int_list_t. Here, I could have a bool_grid_t and a int_grid_t. Should I really start considering writing the kind of macros described in Ian Fisher’s article? I’m not there yet.

I think I’d rather make my grid_t use ints all the time, at the risk of using a bit more memory. This will require very few changes in the code from part 1, and will make the extension to part 2 easy.

Integers are the new booleans

I’m going to start by modifying a test in test_grid:

bool test_grid_set(){
    grid_t* grid = grid_create(10, 20);
    if (!grid_set(grid, coord_create(3, 5), 10))
        return false;
    
    if (grid_set(grid, coord_create(-1, 10), 1))
        return false;

    if (grid_set(grid, coord_create(10, 5), 1))
        return false;

    bool success = (grid->grid[5][3]==10 && grid->grid[0][0]==0);

    grid_destroy(grid);
    return success;
}

I get a FAIL, as expected, and can now refactor with a target in mind. Let’s look at what we need to change in grid.h.

The grid in grid_t will need to be a int** instead of a bool**:

typedef struct {
    int cols;
    int rows;
    int** grid;
} grid_t;

And grid_set needs to accept a int value:

bool grid_set(grid_t* grid, coord_t* coord, bool value);

That should be it. In grid.c now, we need to allocate int*s and ints in the create function, and to set the default value to 0:

grid_t* grid_create(int cols, int rows){
    // ...
    g->grid = malloc(sizeof(int*)*g->rows);

    for (int i=0; i < g->rows; i++){
        g->grid[i] = malloc(sizeof(int)*g->cols);
        for (int j=0; j < g->cols; j++){
            g->grid[i][j] = 0;
        }
    }

    return g;
}

And in grid_set, we actually only need to change the signature. The test now passes, but I still need a small change in grid_count:

int grid_count(grid_t* grid){
    int n = 0;
    for (int i=0; i < grid->rows; i++){
        for (int j=0; j < grid->cols; j++){
            n += grid->grid[i][j];
        }
    }
    return n;
}

Now if I try to run day6, of course, it crashes, so let’s change what needs to be changed there.

In turn_on, set the value to 1. In turn_off, to 0. In toggle:

    grid->grid[coord->y][coord->x] = 1 - grid->grid[coord->y][coord->x];

…and that’s it. Tests run, and I get the same result as before.

Changing the rules

I’m going to need three new methods:

bool grid_increment_cell(grid_t* grid, coord_t* coord){
    int current = grid_get(grid, coord);
    if (current == -1)
        return false;
    return grid_set(grid, coord, current+1);
}

bool grid_decrement_cell(grid_t* grid, coord_t* coord){
    int current = grid_get(grid, coord);
    if (current == -1)
        return false;
    if (current == 0)
        current = 1;
    return grid_set(grid, coord, current-1);
}

bool grid_increment_twice_cell(grid_t* grid, coord_t* coord){
    int current = grid_get(grid, coord);
    if (current == -1)
        return false;
    return grid_set(grid, coord, current+2);
}

With a new method in grid as well:

int grid_get(grid_t* grid, coord_t* coord){
    if (coord->x < 0 || coord->x >= grid->cols || coord->y < 0 || coord->y >= grid->rows)
        return -1;
    
    return grid->grid[coord->y][coord->x];
}

Now we duplicate the apply_instruction code to use the new methods:

void apply_instruction_part_2(grid_t* grid, instruction_t* instruction){
    coord_t* cell = coord_create(-1, -1);
    for (cell->y=instruction->rectangle->tl->y; cell->y <= instruction->rectangle->br->y; cell->y++){
        for (cell->x=instruction->rectangle->tl->x; cell->x <= instruction->rectangle->br->x; cell->x++){
            if (instruction->method == TURN_ON)
                grid_increment_cell(grid, cell);
            else if (instruction->method == TURN_OFF)
                grid_decrement_cell(grid, cell);
            else
                grid_increment_twice_cell(grid, cell);
        }
    }
    coord_destroy(cell);
}

This is where I’d really like to inject references to functions into the mix, but that also seems a bit too much for right now. Instead, in compute_number_of_light, I also add a bool part_two parameter, and do the very ugly:

// ...
if (part_two)
    apply_instruction_part_2(grid, instruction);
else
    apply_instruction(grid, instruction);

And the main becomes:

int main(){
    char *instructions = freadall(INPUT_FILE);
    int lights = compute_number_of_lights(instructions, false);
    printf("Part one: %d lights are on.\n", lights);
    lights = compute_number_of_lights(instructions, true);
    printf("Part two: %d total brightness.\n", lights);
}

Let’s try it. Not the right answer. Let’s try to think about what the answer would be for the test input, so that I can make a test:

turn on 0,0 through 999,999
toggle 0,0 through 999,0
turn off 499,499 through 500,500

That would first put 1 into one million lights, then adding 2 to the first row of one thousand, then decrease back to 0 for four lights in the middle, for a total of 1000000+2*1000-4, which unless I’m mistaken is equal to 1001996. Let’s put that in, and compare my results.

Some print debugging tells me that the first instruction works, but then the second is never executed. Even though the program doesn’t crash. Is it because I’m still running part one first, and for some reason I’m not correctly “resetting” the instructions?

Apparently yes: if I comment out the test on part one, the test on part two succeeds.

Is parse_strings modifying the input string somehow? Ah. Looking at the documentation from strtok, I see:

This function is destructive: it writes the ‘\0’ characters in the elements of the string str.

And since I have:

str_list_t* parse_strings(char* str, char sep){
    char *tok_start = str;
    // ...    
    char *buffer = strtok(tok_start, sep_s);
    // ...
}

tok_start points to the same place as str, so it will change str as well. So I should make a copy of str first. I think I want to do that in parse_strings (and parse_ints):

str_list_t* parse_strings(char* str, char sep){
    char tok_start[strlen(str)+1];
    strcpy(tok_start, str);
    // ...
}

The test now runs fine. And the second star is achieved.

Reading the docs: usually a good idea!