From b7518c96a1aaeffccb489cb47a38a08113b26b1e Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sat, 15 Dec 2018 02:48:35 -0800 Subject: [PATCH] Add solution for Part 1 of day 15. --- day15.cr | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 day15.cr diff --git a/day15.cr b/day15.cr new file mode 100644 index 0000000..8433f14 --- /dev/null +++ b/day15.cr @@ -0,0 +1,267 @@ +require "./common.cr" + +lines = STDIN.gets_to_end.split("\n") +lines.pop + +units = [] of NamedTuple(type: Symbol, power: Int32, hp: Int32, x: Int32, y: Int32) +lines.each_with_index do |line, y| + line.each_char_with_index do |char, x| + units << { type: :elf, power: 3, hp: 200, x: x, y: y } if char == 'E' + units << { type: :goblin, power: 3, hp: 200, x: x, y: y } if char == 'G' + end +end +map = lines.map &.chars + +alias Spot = NamedTuple(x: Int32, y: Int32) + +def nearby_spots(map, x, y) + spots = [] of Spot + spots << { x: x, y: y - 1} if y > 0 + spots << { x: x - 1, y: y } if x > 0 + spots << { x: x + 1, y: y } if x < map[0].size - 1 + spots << { x: x, y: y + 1} if y < map.size - 1 + return spots +end + +def all_open_spots(map, units, type) + open_spots = [] of Spot + units.select &.[:type].==(type) + units.each do |unit| + next unless unit[:type] == type + x = unit[:x] + y = unit[:y] + + open_spots.concat nearby_spots(map, x, y) + end + open_spots.select! do |spot| + map[spot[:y]][spot[:x]] != '#' + end + sort_reading_order(open_spots) + return open_spots +end + +def sort_reading_order(collection) + collection.sort_by! do |item| + item[:y] * 100 + item[:x] + end +end + +def in_range(map, units, spot, type) + nearby_spots(map, spot[:x], spot[:y]).select do |spot| + units.any? { |u| u[:x] == spot[:x] && u[:y] == spot[:y] && u[:type] == type } + end +end + +def occupied(map, units, x, y) + return (map[y][x] == '#') || units.any? { |u| (u[:x] == x) && (u[:y] == y) } +end + +def filter_reachable(map, units, start_spot, open_spots) + open_spots.select do |spot| + shortest_path(map, units, start_spot, spot) + end +end + +def opposite_type(type) + return :goblin if type == :elf + return :elf +end + +def dist(one, other) + (one[:x] - other[:x]).abs + (one[:y] - other[:y]).abs +end + +def path(came_from : Hash(T|R, T|R), start : R) forall T, R + path = [ start ] of (T|R) + while came_from.has_key? start + start = came_from[start] + path << start + end + return path.reverse! +end + +def astart_path(map, units, from, to) + done = Set(Spot).new + open = Set(Spot).new + start = {x: from[:x], y: from[:y] } + open << start + fscore = {} of Spot => Int32 + gscore = { start => 0 } + came_from = {} of Spot => Spot + while !open.empty? + min = open.min_by { |spot| fscore[spot]? || 100000000 } + return path(came_from, min) if min == to + + open.delete min + done << min + + min_gscore = gscore[min]? || (100000000) + nearby_spots(map, min[:x], min[:y]).select { |spot| !occupied(map, units, spot[:x], spot[:y]) }.each do |neighbor| + next if done.includes? neighbor + neighbor_gscore = gscore[neighbor]? || (100000000) + new_score = min_gscore + 1 + open << neighbor if !open.includes? neighbor + next if new_score >= neighbor_gscore + + + came_from[neighbor] = min + gscore[neighbor] = new_score + fscore[neighbor] = new_score + (neighbor[:y] - min[:y]) * 2 + (neighbor[:x] - min[:x]) # + (dist(neighbor, to)) # + ((neighbor[:y] - min[:y]) * 10)) + end + end +end + +def shortest_path(map, units, from, to) + visited = Set(Spot).new + open = Deque(Spot).new(1, { x: from[:x], y: from[:y] } ) + from_hash = {} of Spot => Spot + return [{ x: from[:x], y: from[:y] }] if from[:x] == to[:x] && from[:y] == to[:y] + + while !visited.includes?(to) && !open.empty? + current = open.first + open.delete current + visited << current + x, y = current[:x], current[:y] + + nearby_spots(map, x, y).each do |it| + next if visited.includes? it + visited << it + next if occupied(map, units, it[:x], it[:y]) + from_hash[it] = current + open << it + end + end + return path(from_hash, to) if visited.includes? to + return nil +end + +def moved_unit(map, units, unit) + all_spots = all_open_spots(map, units, opposite_type(unit[:type])) + # puts "all spots: #{all_spots}" + all_spots.select! do |spot| + next true if spot[:x] == unit[:x] && spot[:y] == unit[:y] + next !occupied(map, units, spot[:x], spot[:y]) + end + # puts "spots not occupied: #{all_spots}" + all_paths = all_spots.compact_map do |spot| + shortest_path(map, units, unit, spot) + end + # puts "all paths: #{all_paths}" + path = all_paths.min_by? &.size + # puts "picked path: #{path}" + new_spot = path.try &.[1]? + return unit unless new_spot + return { x: new_spot[:x], y: new_spot[:y], power: unit[:power], hp: unit[:hp], type: unit[:type] } +end + +def unit_at?(units, x, y) + units.select { |u| u[:x] == x && u[:y] == y }.first? +end + +def unit_at(units, x, y) + units.select { |u| u[:x] == x && u[:y] == y }.first +end + +def attacked_unit(map, units, unit) + all_spots = nearby_spots(map, unit[:x], unit[:y]) + enemy_spots = all_spots.select! do |spot| + next false unless enemy = unit_at?(units, spot[:x], spot[:y]) + enemy[:type] == opposite_type(unit[:type]) + end + return nil if enemy_spots.empty? + min_hp = enemy_spots.min_of do |spot| + unit_at(units, spot[:x], spot[:y])[:hp] + end + enemy_spots.select! do |spot| + unit_at(units, spot[:x], spot[:y])[:hp] == min_hp + end + return nil if enemy_spots.empty? + + sort_reading_order(enemy_spots) + attacked_spot = enemy_spots[0] + attacked_index = units.index do |unit| + unit[:x] == attacked_spot[:x] && unit[:y] == attacked_spot[:y] + end + return nil unless attacked_index + attacked_unit = units[attacked_index] + return { attacked_index, { x: attacked_unit[:x], + y: attacked_unit[:y], + power: attacked_unit[:power], + hp: attacked_unit[:hp] - unit[:power], + type: attacked_unit[:type] } } +end + +def draw(map, units) + min_x = 0 + max_x = map[0].size - 1 + min_y = 0 + max_y = map.size - 1 + + (min_y..max_y).each do |y| + (min_x..max_x).each do |x| + unit = unit_at?(units, x, y) + if unit + STDOUT << ((unit[:type] == :elf) ? 'E' : 'G') + else + STDOUT << (map[y][x] == '#' ? '#' : '.') + end + end + puts + end +end + +def step(map, units) + sort_reading_order(units) + index = 0 + while index < units.size + return true if (units.count &.[:type].==(:elf)) == 0 + return true if (units.count &.[:type].==(:goblin)) == 0 + + unit = units[index] + unit = moved_unit(map, units, unit) + units[index] = unit + attacked_u = attacked_unit(map, units, unit) + if attacked_u + attacked_index, attacked_unit = attacked_u + if attacked_unit[:hp] <= 0 + units.delete_at attacked_index + index -= 1 if index >= attacked_index + else + units[attacked_index] = attacked_unit + end + end + index += 1 + end + + return false +end + +def check_win(units) + current_winning = nil + units.each do |u| + if current_winning + return nil if current_winning != u[:type] + else + current_winning = u[:type] + end + end + return current_winning +end + +def winning_score(units, type) + units.select(&.[:type].==(type)).sum &.[:hp] +end + +simulating_units = units.dup +steps = 0 +# draw(map, simulating_units) +until won = check_win(simulating_units) + steps += 1 unless step(map, simulating_units) + # draw(map, simulating_units) + sort_reading_order(simulating_units) + # simulating_units.each do |unit| + # puts "health (#{unit[:type]}): #{unit[:hp]}" + # end + # puts "-- (end of roun #{steps})" +end +puts "#{steps} * #{winning_score(simulating_units, won)} = #{steps * winning_score(simulating_units, won)}"