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
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.