view day18.rb @ 31:47dc75915e91

Implement falling/destroying blocks Implemented part 1 with supports/supported by logic. Implemented part 2 with cascades and summing counts. Code has caching so it is quick _but_ it gives an answer that's too low.
author IBBoard <dev@ibboard.co.uk>
date Wed, 03 Jan 2024 17:00:56 +0000
parents 6b58ddfaed38
children
line wrap: on
line source

#! /usr/bin/env ruby

if ARGV.length < 1 or ARGV.length > 2
        abort("Incorrect arguments - needs input file and optional 'b' for part 2")
elsif not File.exist? (ARGV[0])
	abort("File #{ARGV[0]} did not exist")
end

file = ARGV[0]

instructions = File.open(file, "r").each_line(chomp: true).map(&:split)

Coord = Struct.new(:x,:y, :values)

trench = [Coord.new(0,0, [])]

if ARGV.length == 1
	def decode(instruction)
		[instruction[0], instruction[1]]
	end
else
	def decode(instruction)
		# Offset of 2 because of brackets and hash
		dir_char = case instruction[2][7]
			when "0" then "R"
			when "1" then "D"
			when "2" then "L"
			when "3" then "U"
		end
		num_steps = instruction[2][2,5].to_i(16)
		[dir_char, num_steps]
	end
end

instructions.reduce(trench[0]) do |pos, instruction|
	dir_char, num_steps = decode(instruction)
	# Map to cardinal directions so that we can use the day 10 border crossing algorithm
	# for finding "inside" nodes
	case dir_char
		when "R"
			x_change = 1
			y_change = 0
			prev_dir = :east
			next_dir = :west
		when "L"
			x_change = -1
			y_change = 0
			prev_dir = :west
			next_dir = :east
		when "U"
			x_change = 0
			y_change = -1
			prev_dir = :north
			next_dir = :south
		when "D"
			x_change = 0
			y_change = 1
			prev_dir = :south
			next_dir = :north
	end

	num_steps.to_i.times do |i|
		pos.values << prev_dir
		pos = Coord.new(pos.x + x_change, pos.y + y_change, [next_dir])
		trench << pos
	end
	pos
end

x_min, x_max = trench.map(&:x).minmax
y_min, y_max = trench.map(&:y).minmax

puts x_min, y_min

if ARGV.length > 1
	puts "We can't brute force this. Answers online suggest shoelace formula and Pick's theorem"
	puts "So we'll call it quits and skip this"
	return
end

dig_map = Hash.new
trench.each do |pos|
	new_x = pos.x - x_min
	new_y = pos.y - y_min
	coords = [new_x, new_y]
	if dig_map.key? (coords)
		dig_map[coords] += pos.values
	else
		dig_map[coords] = pos.values
	end
	dig_map[coords].sort!
end


State = Struct.new(:crossed_lines, :inside_count, :last_switch)
in_out_transition = [:north, :south]

state = State.new(0,0,nil)

cells_inside = (y_max - y_min + 1).times do |y|
	state.last_switch = nil
	(x_max - x_min + 1).times do |x|
		cell = dig_map[[x,y]]
		if cell
			#puts "#{x},#{y} - #{cell}"
			# Same algorithm as Day 10 EXCEPT we count the trench/border as well
			state.inside_count += 1
			if cell == in_out_transition
				state.crossed_lines += 1
			elsif cell != [:east, :west]
				this_switch = (cell & in_out_transition)
				if state.last_switch.nil?
					state.last_switch = this_switch
				else
					if this_switch != state.last_switch
						state.crossed_lines += 1
					end
					state.last_switch = nil
				end
			end
		elsif state.crossed_lines % 2.0 != 0
			#puts "#{x},#{y} - inside"
			state.inside_count += 1
		else
			#puts "#{x},#{y} - crossed #{state.crossed_lines} lines"
		end
		state
	end
	state
end
puts state.inside_count