# Advent of Code 2020 day 24

The solution to day 24 was very similar to my solution to day 17 and most of the code for today is much the same as then.

## Part 1

Parsing and data structures were the bulk of the work for part 1. I thought about using the hexGrid library, but decided that I wouldn't take too much advantage of it. Instead I used the same coordinate system and implemented it myself. East-west rows are constant y, and rows from south-west to north-east are constant x. Moving south-east or north-west means changing both x and y.

A Tile is defined by its location, given as a V2 two-dimensional vector, and a Grid is a Set of black tiles. There are six Directions, and the function delta converts a Direction into a change in position. Finally, I fix how the elements of a Tile combine to make parsing a bit easier.

data Direction = NE | E | SE | SW | W | NW
deriving (Show, Eq, Enum, Bounded)

type Tile = V2 Int -- x, y
type Grid = S.Set Tile

instance Semigroup Int where
(<>) = (+)

instance Monoid Int where
mempty = 0

delta :: Direction -> Tile
delta NE = V2  1  0
delta E  = V2  0  1
delta SE = V2 -1  1
delta SW = V2 -1  0
delta W  = V2  0 -1
delta NW = V2  1 -1


Parsing the input file has a couple of subtleties. One is the ordering of elements in the choice, so that "n" and "s" characters are consumed before "e" and "w". The other is the use of the semigroup and monoid instances to allow foldMap to combine all the steps into the position at the end of each path.

tilesP = tileP sepBy endOfLine
tileP = foldMap delta <$> many1 stepP stepP = choice [neP, nwP, seP, swP, eP, wP] neP = "ne" *> pure NE nwP = "nw" *> pure NW seP = "se" *> pure SE swP = "sw" *> pure SW eP = "e" *> pure E wP = "w" *> pure W  All that's left is to create the grid by flipping tiles, then count how many are black. main :: IO () main = do text <- TIO.readFile "data/advent24.txt" let walks = successfulParse text let grid0 = foldr flipTile S.empty walks print$ part1 grid0

part1 grid0 = S.size grid0

flipTile :: Tile -> Grid -> Grid
flipTile tile tiles
| tile S.member tiles = S.delete tile tiles
| otherwise = S.insert tile tiles


## Part 2

This is very similar to day 17. As I defined the directions as both Enum and Bounded, I can generate all the neighbours of a point by enumerating all the directions.

neighbourSpaces :: Tile -> Grid
neighbourSpaces here = S.fromList $map nbrSpace [minBound .. maxBound] -- [NE .. NW] where nbrSpace d = here ^+^ (delta d)  After that, it's all much the same as before, but with the grid updating by adding newly black tiles and removing newly white tiles. part2 grid0 = S.size$ (iterate update  grid0) !! 100

countOccupiedNeighbours :: Tile -> Grid -> Int
countOccupiedNeighbours cell grid =
S.size $S.intersection grid$ neighbourSpaces cell

tileBecomesWhite :: Grid -> Tile -> Bool
tileBecomesWhite grid cell = black && ((nNbrs == 0) || (nNbrs > 2))
where black = cell S.member grid
nNbrs = countOccupiedNeighbours cell grid

tileBecomesBlack :: Grid -> Tile -> Bool
tileBecomesBlack grid cell = white && (nNbrs == 2)
where white = cell S.notMember grid
nNbrs = countOccupiedNeighbours cell grid

update :: Grid -> Grid
update grid = (grid S.union newBlacks) S.difference newWhites
where neighbours = (S.foldr mergeNeighbours S.empty grid) S.difference grid
mergeNeighbours cell acc = S.union acc \$ neighbourSpaces cell
newWhites = S.filter (tileBecomesWhite grid) grid
newBlacks = S.filter (tileBecomesBlack grid) neighbours


## Code

You can find the code here or on GitLab.