DEV Community

Cover image for Advent of code - Day 24
Quentin Ménoret
Quentin Ménoret

Posted on

Advent of code - Day 24

Are you participating in the Advent of code this year?

If you don't know what the advent of code is, it's a website where you'll find a daily challenge (every day it gets harder). It's a really fun event, you should participate!

I try to solve the exercises using either JavaScript or TypeScript and will share my solutions daily (with one day delay so no one can cheat!). I only share the solution for the second part.


Another game of life, but with hexagons this time! The trick here was to make sure you only process the black tiles and their adjacent tiles. Just drop all white tiles, they are useless! Once I figured that out, performance dropped to a ok level (this code runs in about 10 seconds).

Here is my solution for day #24:

interface Position {
  e: number
  se: number
  sw: number
  w: number
  nw: number
  ne: number
}

export function extract(str: string): (keyof Position)[] {
  if (str.length === 0) return []
  if (['e', 'w'].includes(str[0])) return [str[0] as keyof Position, ...extract(str.slice(1))]
  return [str.slice(0, 2) as keyof Position, ...extract(str.slice(2))]
}

function count(position: (keyof Position)[]): Position {
  return position.reduce(
    (acc, value) => {
      acc[value] += 1
      return acc
    },
    {
      e: 0,
      se: 0,
      sw: 0,
      w: 0,
      nw: 0,
      ne: 0,
    },
  )
}

interface Tile {
  position: Position
  state: State
}

enum State {
  Black,
  White,
}

const opposition: Record<keyof Position, keyof Position> = {
  e: 'w',
  w: 'e',
  nw: 'se',
  se: 'nw',
  sw: 'ne',
  ne: 'sw',
}

const complementaries: Record<keyof Position, (keyof Position)[]> = {
  e: ['se', 'ne'],
  w: ['sw', 'nw'],

  se: ['sw', 'e'],
  sw: ['se', 'w'],
  nw: ['ne', 'w'],
  ne: ['nw', 'e'],
}

function rationalize(position: Position): Position {
  for (let key of (Object.keys(opposition) as unknown) as (keyof Position)[]) {
    if (position[key] < 0) {
      return rationalize({
        ...position,
        [key]: 0,
        [opposition[key]]: position[opposition[key]] - position[key],
      })
    }
    if (position[key] > 0 && position[opposition[key]] > 0) {
      return rationalize({
        ...position,
        [key]: position[key] - 1,
        [opposition[key]]: position[opposition[key]] - 1,
      })
    }
  }
  for (let key of (Object.keys(complementaries) as unknown) as (keyof Position)[]) {
    if (complementaries[key].every((comp) => position[comp] > 0)) {
      return rationalize({
        ...position,
        [complementaries[key][0]]: position[complementaries[key][0]] - 1,
        [complementaries[key][1]]: position[complementaries[key][1]] - 1,
        [key]: position[key] + 1,
      })
    }
  }
  return position
}

function print(position: Position): string {
  return `${position.e},${position.se},${position.sw},${position.w},${position.nw},${position.ne}`
}

export function moveTo(newCenter: Position, tile: Position): Position {
  return rationalize(
    ((Object.keys(tile) as unknown) as (keyof Position)[]).reduce(
      (acc: Position, key) => {
        acc[key] = tile[key] - newCenter[key]
        return acc
      },
      {
        e: 0,
        se: 0,
        sw: 0,
        w: 0,
        nw: 0,
        ne: 0,
      },
    ),
  )
}

interface PotentialTile {
  adjacentBlacks: number
  position: Position
}

export function areAdjacent(tile: Tile, adjacent: Tile) {
  const newPosition = moveTo(tile.position, adjacent.position)
  const keys = (Object.keys(newPosition) as unknown) as (keyof Position)[]
  return keys.filter((key) => newPosition[key] === 1).length === 1 && keys.every((key) => newPosition[key] <= 1)
}

export function findAdjacent(tile: Tile): Position[] {
  return ((Object.keys(tile.position) as unknown) as (keyof Position)[]).map((key) => {
    return {
      ...tile.position,
      [key]: tile.position[key] + 1,
    }
  })
}

function runTurn(allTiles: Tile[]) {
  const potentialNewTiles: Record<string, PotentialTile> = {}
  const processed = new Set()

  const indexedTiles = allTiles.reduce((acc: Record<string, Tile>, v) => {
    acc[print(v.position)] = v
    return acc
  }, {})

  const existingTiles = allTiles
    .map((tile) => {
      processed.add(print(tile.position))

      const adjacents = findAdjacent(tile)
      adjacents.forEach((adjacentPosition) => {
        const actualPosition = rationalize(adjacentPosition)
        const printedPosition = print(actualPosition)
        if (processed.has(printedPosition)) return
        potentialNewTiles[printedPosition] = potentialNewTiles[printedPosition] || {
          position: actualPosition,
          adjacentBlacks: 0,
        }
        potentialNewTiles[printedPosition].adjacentBlacks += 1
      })

      const numberOfBlackAdjacents = adjacents
        .map((position) => indexedTiles[print(rationalize(position))])
        .filter(Boolean)
        .filter((adjacent) => adjacent.state === State.Black).length

      return {
        ...tile,
        state: numberOfBlackAdjacents === 1 || numberOfBlackAdjacents === 2 ? State.Black : State.White,
      }
    })
    .filter((tile) => tile.state === State.Black)

  const newTiles = Object.keys(potentialNewTiles)
    .filter((key) => !processed.has(key))
    .filter((key) => potentialNewTiles[key].adjacentBlacks === 2)
    .map((key) => {
      return {
        position: potentialNewTiles[key].position,
        state: State.Black,
      }
    })

  return [...existingTiles, ...newTiles]
}

const positions = input
  .split('\n')
  .map(extract)
  .map(count)
  .map(rationalize)
  .reduce((acc: Record<string, Tile>, value) => {
    acc[print(value)] = acc[print(value)] || { position: value, state: State.White }
    acc[print(value)].state = acc[print(value)].state === State.Black ? State.White : State.Black
    return acc
  }, {})

const tiles = Object.keys(positions)
  .map((key) => positions[key])
  .filter((tile) => tile.state === State.Black)
let currentTiles = tiles

for (let i = 0; i < 101; i++) {
  console.log(`Turn: ${i}: ${currentTiles.filter((t) => t.state === State.Black).length}`)
  currentTiles = runTurn(currentTiles)
}

Enter fullscreen mode Exit fullscreen mode

Feel free to share your solution in the comments!


Photo by Markus Spiske on Unsplash

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

SurveyJS custom survey software

JavaScript Form Builder UI Component

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay