Day 16 Part 2

Summary

See Part 1

  • The beam now can start on any edge tile
  • Find the path that energizes the most tiles

Function

Luckily, this challenge can reuse 100% from Part 1. To start with this I throw Part 1’s main loop into a function. I’ll pass the starting position, the direction, and which function to call as arguments.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
function calculate_energized(data: tile[][], start_x: number, start_y: number, start_direction: string, start_call: Function): number{
  let x: number = start_x;
  let y: number = start_y;
  let direction: string = start_direction;
  let call: Function = start_call

  let lights: light_point[] = [];

  while (true){
    let tile: tile|undefined = call(data, x, y);

    if (tile !== undefined){

      if (['\\', '/'].indexOf(tile.contents) !== -1){
        const result: call_dict = mirror_map.get(`${direction}_${tile.contents}`)
        call = result['func'];
        direction = result['direction'];
      }else if (tile.contents == '|' && !tile.reflected){
        lights.push({
          contents: tile.contents,
          x: tile.x,
          y: tile.y,
          func: go_north,
          direction: 'N',
        });
        call = go_south;
        direction = 'S';
        tile.reflected = true; // anti-loops
      }else if (tile.contents == '-' && !tile.reflected){
        lights.push({
          contents: tile.contents,
          x: tile.x,
          y: tile.y,
          func: go_east,
          direction: 'E',
        });
        call = go_west;
        direction = 'W';
        tile.reflected = true; // anti-loops
      }else{
        // presumed a `|` or `-` previously reflected
        tile = undefined;
      }
    }

    if (tile !== undefined){
      // update coordinates
      x = tile.x;
      y = tile.y;
    }else{
      if (lights.length){
        // hit an edge, use a point from lights
        let light: light_point = lights.pop();

        if (debug && (debug_count == null || debug_count <= i )){
          console.log(light);
        }

        // update using stored values
        direction = light.direction;
        call = light['func'];
        x = light.x;
        y = light.y;
      }else{
        // empty => all done
        break;
      }
    }
  };

  let sum: number = 0;
  data.forEach((row) => {
    sum += row.filter((tile) => tile.energized).length;
  });

  return sum;
}

Horizontal Loop

All that’s left is to loop through all the combinations of edges. I started with horizontal starts, so east or west, as they share the y start coordinate moving and the x coordinate being static. The most important thing to remember is that it must start 1 coordinate off the board. If moving east it must start with -1. If moving west it must be data.length, index starts at 0.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let sum: number = 0;

// used in both horizontal + vertical
let direction: string;
let call: Function;
let x: number;
let y: number;

// horizontal starts:
for (let i = 0; i < 2; i++){
  if (i == 0){
    direction = 'E';
    call = go_east;
    x = -1;
  }else{
    direction = 'W';
    call = go_west;
    x = data.length;
  }

  for (let y = 0; y < data.length; y++){
    let local_sum: number = calculate_energized(data, x, y, direction, call)
    if (sum < local_sum){
      console.log(`(${x}, ${y}) ${direction} => Local_Sum: ${local_sum}`);
      sum = local_sum
    }
  }
}

Resetting Data

As I found out, the tile’s energized and reflected attributes need to be reset every loop or the sum will keep getting bigger! Here’s a quick function that does that:

1
2
3
4
5
6
7
8
9
function clear_energized(data: tile[][]): tile[][]{
  data.forEach((row) => {
    row.forEach((tile) => {
      tile.energized = false;
      tile.reflected = false;
    });
  });
  return data;
}

..and here is implementing it in the horizontal loop

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Horizontal starts:
for (let i = 0; i < 2; i++){

  // ...

  for (let y = 0; y < data.length; y++){
    let local_sum: number = calculate_energized(data, x, y, direction, call);
    if (sum < local_sum){
      console.log(`(${x}, ${y}) ${direction} => Local_Sum: ${local_sum}`);
      sum = local_sum;
    }
    data = clear_energized(data);
  }
}

Vertical Loop

Vertical is almost the same, but the start coordinate moving is the x and the y coordinate is the one moving. Add it right after the horizontal start and output the sum.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Vertical starts:
for (let i = 0; i < 2; i++){
  if (i == 0){
    direction = 'S';
    call = go_south;
    y = -1;
  }else{
    direction = 'N';
    call = go_north;
    y = data.length;
  }

  for (let x = 0; x < data.length; x++){
    let y: number = 0;
    let local_sum: number = calculate_energized(data, x, y, direction, call);
    if (sum < local_sum){
      console.log(`(${x}, ${y}) ${direction} => Local_Sum: ${local_sum}`);
      sum = local_sum;
    }
    data = clear_energized(data);
  }
}

Conclusion

I’m surprised this worked so easily. Usually Part 2s add so many more iterations that any Part 1 needs some rework to get it up to snuff or you’ll be waiting for 20 seconds!- ..and that’s when you know you messed up somewhere. I had one hangup that was resetting tile attributes, and I should have saw that from that start.

comments powered by Disqus