# Advent of Code 2022, Day 23: Unstable Diffusion

## Part A

On Day 23 we need to simulate the movements of multiple elves on the map. Our input looks like this:

``````....#..
..###.#
#...#.#
.#...##
#.###..
##.#.##
.#..#..
``````

Each dot represents empty space and hash represents some elf current position. Elves can move according to the list of rules that are described on the puzzle page.

This task is not that challanging, you just need to implement rules correctly:

``````class Elf
attr_accessor :x, :y, :moves

def initialize(x, y, map)
@x = x
@y = y
@moves = [:north, :south, :west, :east]
@map = map
end

def moves
@moves
end

def move(nx, ny)
set(@x, @y, false)
set(nx, ny, true)

@x = nx
@y = ny
end

def can_move?(direction)
case direction
when :north
[get(@x - 1, @y - 1), get(@x, @y - 1), get(@x + 1, @y - 1)] == [false, false, false]
when :south
[get(@x - 1, @y + 1), get(@x, @y + 1), get(@x + 1, @y + 1)] == [false, false, false]
when :west
[get(@x - 1, @y - 1), get(@x - 1, @y), get(@x - 1, @y + 1)] == [false, false, false]
when :east
[get(@x + 1, @y - 1), get(@x + 1, @y), get(@x + 1, @y + 1)] == [false, false, false]
end
end

def new_position(direction)
case direction
when :north
[@x, @y - 1]
when :south
[@x, @y + 1]
when :west
[@x - 1, @y]
when :east
[@x + 1, @y]
end
end

def elves_nearby?
[
[@x - 1, @y - 1],
[@x, @y - 1],
[@x + 1, @y - 1],
[@x - 1, @y],
[@x + 1, @y],
[@x - 1, @y + 1],
[@x, @y + 1],
[@x + 1, @y + 1]
].any? { |(x, y)| get(x, y) }
end
end

def get(x, y)
!!@map[[x, y]]
end

def set(x, y, value)
@map[[x, y]] = value
end

def draw_map
min_x, max_x = @elves.map { |elf| elf.x }.minmax
min_y, max_y = @elves.map { |elf| elf.y }.minmax

[0, min_y].min.upto([@map_y - 1, max_y].max) do |y|
row = ""
[0, min_x].min.upto([max_x, @map_x - 1].max) do |x|
row += get(x, y) ? "#" : "."
end
puts row
end
end

def area
min_x, max_x = @elves.map { |elf| elf.x }.minmax
min_y, max_y = @elves.map { |elf| elf.y }.minmax

result = 0

min_y.upto(max_y) do |y|
min_x.upto(max_x) do |x|
result += 1 unless get(x, y)
end
end

result
end

data = File.readlines("23.txt", chomp: true).map { |row| row.split("") }
@elves = []
@map = {}
@map_x = data[0].size
@map_y = data.size

data.each_with_index do |row, y|
row.each_with_index do |tile, x|
if tile == "#"
@elves.push(Elf.new(x, y, @map))
set(x, y, true)
end
end
end

10.times do |round|
proposals = {}

@elves.each_with_index do |elf, index|
if elf.elves_nearby?
direction = elf.moves.detect { |direction| elf.can_move?(direction) }
new_position = elf.new_position(direction)

proposals[new_position] ||= []
proposals[new_position].push(elf)
end

elf.moves.rotate!
end

proposals.each do |(x, y), elves|
if elves.size == 1
elves[0].move(x, y)
end
end
end

puts area
``````

## Part B

This part is very similar. In first part we need to simulate 10 rounds and in second part we need to simulate until all elves are spread out.

Here is my full code:

``````class Elf
attr_accessor :x, :y, :moves

def initialize(x, y, map)
@x = x
@y = y
@moves = [:north, :south, :west, :east]
@map = map
end

def moves
@moves
end

def move(nx, ny)
set(@x, @y, false)
set(nx, ny, true)

@x = nx
@y = ny
end

def can_move?(direction)
case direction
when :north
[get(@x - 1, @y - 1), get(@x, @y - 1), get(@x + 1, @y - 1)] == [false, false, false]
when :south
[get(@x - 1, @y + 1), get(@x, @y + 1), get(@x + 1, @y + 1)] == [false, false, false]
when :west
[get(@x - 1, @y - 1), get(@x - 1, @y), get(@x - 1, @y + 1)] == [false, false, false]
when :east
[get(@x + 1, @y - 1), get(@x + 1, @y), get(@x + 1, @y + 1)] == [false, false, false]
end
end

def new_position(direction)
case direction
when :north
[@x, @y - 1]
when :south
[@x, @y + 1]
when :west
[@x - 1, @y]
when :east
[@x + 1, @y]
end
end

def elves_nearby?
[
[@x - 1, @y - 1],
[@x, @y - 1],
[@x + 1, @y - 1],
[@x - 1, @y],
[@x + 1, @y],
[@x - 1, @y + 1],
[@x, @y + 1],
[@x + 1, @y + 1]
].any? { |(x, y)| get(x, y) }
end
end

def get(x, y)
!!@map[[x, y]]
end

def set(x, y, value)
@map[[x, y]] = value
end

def draw_map
min_x, max_x = @elves.map { |elf| elf.x }.minmax
min_y, max_y = @elves.map { |elf| elf.y }.minmax

[0, min_y].min.upto([@map_y - 1, max_y].max) do |y|
row = ""
[0, min_x].min.upto([max_x, @map_x - 1].max) do |x|
row += get(x, y) ? "#" : "."
end
puts row
end
end

def area
min_x, max_x = @elves.map { |elf| elf.x }.minmax
min_y, max_y = @elves.map { |elf| elf.y }.minmax

result = 0

min_y.upto(max_y) do |y|
min_x.upto(max_x) do |x|
result += 1 unless get(x, y)
end
end

result
end

data = File.readlines("23.txt", chomp: true).map { |row| row.split("") }
@elves = []
@map = {}
@map_x = data[0].size
@map_y = data.size

data.each_with_index do |row, y|
row.each_with_index do |tile, x|
if tile == "#"
@elves.push(Elf.new(x, y, @map))
set(x, y, true)
end
end
end

round = 0

while true
proposals = {}
elves_skipped = 0

@elves.each_with_index do |elf, index|
if elf.elves_nearby?
direction = elf.moves.detect { |direction| elf.can_move?(direction) }

if direction
new_position = elf.new_position(direction)

proposals[new_position] ||= []
proposals[new_position].push(elf)
else
elves_skipped += 1
end
else
elves_skipped += 1
end

elf.moves.rotate!
end

if elves_skipped == @elves.size
puts round + 1
break
end

proposals.each do |(x, y), elves|
if elves.size == 1
elves[0].move(x, y)
end
end

round += 1
end
``````

I also included code for drawing the current map so it is possible to see how elves are positioned at each round.