Advent of Code, Day 24 – Black Lives Matter

Day 24 of Advent of Code is here!

Black Lives Matter

Today’s puzzle uses two color options, black and white. And as we step through the rules we are changing tiles from black to white, and visa-versa. At some point I need to find how many black neighbors a tile has and change it based on that count. This felt all too real, and insensitive. I did not want to work with these color choices and terms. Instead, I completed it by using green and red instead. It’s Christmas time! Let’s stick with Christmas colors ;)

Black Lives Matter every day, not just when our news media says it does.

Day 24: Lobby Layout

Part I

Thankfully we’re not playing the crab anymore. Now we’re in a lobby of a resort. The floor is being renovated with hexagon (6 sided) tiles are being put down. Using some directional instructions, we can determine which tiles should be red and which should be green.

I had to draw this out, as a 6 sided shape is not something we normally work with. The above coords are e for east, ne for northeast and so on…

Then I created my coordinate system so x and y would be the middle of a hexagon, like

If we were to go east we would move x up (right) two. Going north would move y up one. Going north east would add one to both x and y.

Now let’s see about some data. They didn’t make it easy:

Let’s get the data

const fs = require('fs')
let data = fs.readFileSync('./input.txt', 'utf-8').trim().split('\n')

const tiles = new Map()
let greenCounter = 0

Yup, the same method to get the data as normal. I created a new Map called tiles to store each tile we create, so we can track whether they’re green or red.

Now lets loop through the data

data.forEach(row => {
  const directions = row.match(/(se)|(ne)|(sw)|(nw)|(e)|(w)/g)

  let x = 0
  let y = 0
  directions.forEach(dir => {
    switch(dir) {
      case 'e':
        x += 2
        break
      case 'w':
        x -= 2
        break
      case 'se':
        x += 1
        y -= 1
        break
      case 'sw':
        x -= 1
        y -= 1
        break
      case 'ne':
        x += 1
        y += 1
        break
      case 'nw':
        x -= 1
        y += 1
        break
      default:
        break
    }
  })
  const theTile = `x${x}y${y}`;
  if(tiles.get(theTile)) {
    tiles.set(theTile, 'red')
    greenCounter -= 1
  } else {
    tiles.set(theTile, 'green')
    greenCounter += 1
  }
})
  • First let’s get the directions. These long strings of undelimited directions was intimidating at first, but regex to the rescue! This little regex ensures we will get each coordinate, in their right order
    • (se)|(ne)|(sw)|(nw)|(e)|(w) – just looks for those specific characters and returns them. Regex executes in order, by putting e and w last they won’t be included from the ne or sw instances.
  • Then we set up x and y vars to store the movement as we go.
  • Now let’s loop through the directions for the current row.
  • I use a switch() off of the direction, and set the x and y vars accordingly.
  • After it loops through all of the directions my x and y will be the tile we end on.
  • I create a unique string containing the coordinates.
    • To ensure it’s unique, I added x and y as values in there. And sort of delimiter would work. If I didn’t, I couldn’t guarantee the values would be unique. If x is 11 and y is 4, and I just concatenated those, the value would be 114. That could also match x 1 y 14.
  • Next we check if the tiles Map() has this value already. If it does, we change its color to red.
  • If we don’t have this tile already, it defaults to green.
  • You’ll also notice a little greenCounter which collects our answer.

To get the final answer, we just write it out

console.log('There are', greenCounter, 'green tiles')

Part II

No, no crazy “now do this a gazillion times” this day. Instead, we add some new rules and run through it a few more times. Based on the number of neighbors of a color, a tile can change colors. We will then run through this 100 times.

Most of part 1 lives on here, with some refactoring:

const getXYfromDir = (x, y, direction) => {
  let xx = x
  let yy = y
  switch(direction) {
    case 'e':
      xx += 2
      break
    case 'w':
      xx -= 2
      break
    case 'se':
      xx += 1
      yy -= 1
      break
    case 'sw':
      xx -= 1
      yy -= 1
      break
    case 'ne':
      xx += 1
      yy += 1
      break
    case 'nw':
      xx -= 1
      yy += 1
      break
    default:
      break
  }
  return { x: xx, y: yy }
}

I ripped out the above from part 1 and created its own function. This will be used a few times.

data.forEach(row => {
  const directions = row.match(/(se)|(ne)|(sw)|(nw)|(e)|(w)/g)

  let x = 0
  let y = 0
  directions.forEach(dir => {
    const newCoords = getXYfromDir(x, y, dir)
    x = newCoords.x
    y = newCoords.y
  })
  const theTile = `x${x}y${y}`;
  if(tiles.get(theTile)) {
    tiles.set(theTile, 'red')
    greenCounter -= 1
  } else {
    tiles.set(theTile, 'green')
    greenCounter += 1
  }
})

This is also from part 1, now refactored to use the getXYfromDir function.

Now we get looping, 100 times.

const neighborPositions = ['e', 'w', 'se', 'sw', 'ne', 'nw']

Before that, let’s define all the neighbors

for(let loop = 0; loop < 100; loop += 1) {
  greenCounter = 0
  const expandTiles = new Map()

Using a for() loop, I reset some variables on each loop. More to come on these

  tiles.forEach((value, key) => {
    expandTiles.set(key, value)

    const [str, x, y] = key.match(/x([\-0-9]*)y([\-0-9]*)/)

    neighborPositions.forEach(dir => {
      const neigh = getXYfromDir(Number(x), Number(y), dir)
      const theTile = `x${neigh.x}y${neigh.y}`;
      if(!tiles.get(theTile)) expandTiles.set(theTile, 'red')
    })
  })

Here we expand our current tiles to include new neighbors.

  • As we loop through the tiles, we’re adding those tiles to expandTiles.
  • Then we find the x y for this tile
  • Then loop through all the neighborPositions and find each neighbor using getXYfromDir.
  • If this neighbor does not exist in tiles we add it to expandTiles
    • This will be the last we use tiles in this loop, until we set it to a new object at the end.
    • We can’t just add new neighbors to tiles as that would loop indefinitely (yes, I learned that by doing that)

Once we have the new set of tiles expandTiles, we loop through and flip their colors

  const nextDayTiles = new Map()
  expandTiles.forEach((value, key) => {
    const numGreenNeighbors = getNeighborCount(key)
    if(value === 'green') {
      if(numGreenNeighbors === 0 || numGreenNeighbors > 2) {
        nextDayTiles.set(key, 'red')
      } else {
        greenCounter += 1
        nextDayTiles.set(key, value)
      }
    } else if(value === 'red') {
      if(numGreenNeighbors === 2) {
        nextDayTiles.set(key, 'green')
        greenCounter += 1
      } else {
        nextDayTiles.set(key, value)
      }
    }
  })
  • I create a new nextDayTiles Map() to store the tiles as we swap them.
  • Next we loop through all of the expandTiles
  • We find the number of neighbors that are green (getNeighborCount is below)
  • If the current tile is green
    • If the neighbor count matches the criteria, this tile is now red and added to nextDayTiles
    • Otherwise, just add the tile to nextDayTiles
  • If the current tile is red
    • If the neighbor count matches the criteria, this tile is now green and added
    • Otherwise, we just add this tile

At the end, nextDayTiles, is loaded up with all tiles for the day, and the current colors.

const getNeighborCount = (tile) => {
    const [str, x, y] = tile.match(/x([\-0-9]*)y([\-0-9]*)/)
    let greenCount = 0
    
    neighborPositions.forEach(dir => {
      const neigh = getXYfromDir(Number(x), Number(y), dir)
      const theTile = `x${neigh.x}y${neigh.y}`;
      if(expandTiles.get(theTile)) {
        if(expandTiles.get(theTile) === 'green') greenCount += 1
      }
    })
      return greenCount
  }

I am getting the neighbor count in getNeighborCount

  • We grab the current x y coordinates of the tile
  • Then we loop through all of the neighborPositions, getting each of their x y and getting their value from expandTiles
  • Then we return the number of green
  tiles = nextDayTiles
  console.log('Day', loop + 1, 'There are', greenCounter, 'green tiles')
}

Finally, at the bottom of the loop, I set tiles to nextDayTiles and print out how many green there are for this day. When the code finishes, it’ll show Day 100’s answer

How did it go for you?

How did you find the answer on your own? How did you do it? Anything stump you? I’d love to hear how you did! Please comment below! I have also loaded up just the challenge and code from this and my other days on github, you can follow along there too: pretty Git pages or the code in the repo.

Series Navigation<< Advent of Code, Day 25Advent of Code, Day 23 >>

Leave a Reply

Up ↑

%d bloggers like this: