Compare commits
3 Commits
ca92e7cca0
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| edfd7a1ecb | |||
| 6ff1d97c91 | |||
| b7518c96a1 |
282
day15.cr
Normal file
282
day15.cr
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
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
|
||||||
|
elf_death_count = 0
|
||||||
|
while index < units.size
|
||||||
|
return nil if (units.count &.[:type].==(:elf)) == 0
|
||||||
|
return nil 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
|
||||||
|
elf_death_count += 1 if attacked_unit[:type] == :elf
|
||||||
|
units.delete_at attacked_index
|
||||||
|
index -= 1 if index >= attacked_index
|
||||||
|
else
|
||||||
|
units[attacked_index] = attacked_unit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
index += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return elf_death_count
|
||||||
|
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
|
||||||
|
|
||||||
|
power = 3
|
||||||
|
loop do
|
||||||
|
simulating_units = units.dup.map do |unit|
|
||||||
|
next unit unless unit[:type] == :elf
|
||||||
|
{ x: unit[:x], y: unit[:y], hp: unit[:hp], type: :elf, power: power }
|
||||||
|
end
|
||||||
|
steps = 0
|
||||||
|
died = false
|
||||||
|
# draw(map, simulating_units)
|
||||||
|
until won = check_win(simulating_units) || died
|
||||||
|
result = step(map, simulating_units)
|
||||||
|
if result
|
||||||
|
steps += 1
|
||||||
|
died = result > 0
|
||||||
|
end
|
||||||
|
# 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 "(power: #{power}) #{steps} * #{winning_score(simulating_units, won)} = #{steps * winning_score(simulating_units, won)}"
|
||||||
|
break if !died && won != :goblin
|
||||||
|
power += 1
|
||||||
|
end
|
||||||
101
day16.cr
Normal file
101
day16.cr
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
require "./common.cr"
|
||||||
|
|
||||||
|
lines = File.read("day16_input").split "\n"
|
||||||
|
lines.pop
|
||||||
|
count = 0
|
||||||
|
end_index = 0
|
||||||
|
start_index = 0
|
||||||
|
|
||||||
|
lines.each_with_index do |line, i|
|
||||||
|
if line.size == 0
|
||||||
|
count += 1
|
||||||
|
else
|
||||||
|
count = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if count == 3
|
||||||
|
end_index = i - 3
|
||||||
|
start_index = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
registers = Array(Int32).new(4, 0)
|
||||||
|
OPCODES = {
|
||||||
|
"addr" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] + regs[b] },
|
||||||
|
"addi" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] + b },
|
||||||
|
"mulr" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] * regs[b] },
|
||||||
|
"muli" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] * b },
|
||||||
|
"banr" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] & regs[b] },
|
||||||
|
"bani" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] & b },
|
||||||
|
"borr" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] | regs[b] },
|
||||||
|
"bori" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] | b },
|
||||||
|
"setr" => ->(regs : Array(Int32), a : Int32, b : Int32) { regs[a] },
|
||||||
|
"seti" => ->(regs : Array(Int32), a : Int32, b : Int32) { a },
|
||||||
|
"gtir" => ->(regs : Array(Int32), a : Int32, b : Int32) { (a > regs[b]) ? 1 : 0 },
|
||||||
|
"gtri" => ->(regs : Array(Int32), a : Int32, b : Int32) { (regs[a] > b) ? 1 : 0 },
|
||||||
|
"gtrr" => ->(regs : Array(Int32), a : Int32, b : Int32) { (regs[a] > regs[b]) ? 1 : 0 },
|
||||||
|
"eqir" => ->(regs : Array(Int32), a : Int32, b : Int32) { (a == regs[b]) ? 1 : 0 },
|
||||||
|
"eqri" => ->(regs : Array(Int32), a : Int32, b : Int32) { (regs[a] == b) ? 1 : 0 },
|
||||||
|
"eqrr" => ->(regs : Array(Int32), a : Int32, b : Int32) { (regs[a] == regs[b]) ? 1 : 0 },
|
||||||
|
}
|
||||||
|
|
||||||
|
def find_possible(instr, before, after)
|
||||||
|
OPCODES.select do |name, code|
|
||||||
|
sample = before.dup
|
||||||
|
sample[instr[3]] = code.call(sample, instr[1], instr[2])
|
||||||
|
sample == after
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ARRAY_REGEX = /[^:]+:\s*\[([^\]]*)\]/
|
||||||
|
|
||||||
|
class String
|
||||||
|
def extract_array
|
||||||
|
self.match(ARRAY_REGEX).not_nil![1].split(", ").map &.to_i32
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
index = 0
|
||||||
|
count_more_three = 0
|
||||||
|
possibilities = Array(Set(String)).new(16) { Set(String).new }
|
||||||
|
while index < end_index
|
||||||
|
start_array = lines[index].extract_array
|
||||||
|
end_array = lines[index + 2].extract_array
|
||||||
|
instruction = lines[index + 1].split(" ").map &.to_i32
|
||||||
|
|
||||||
|
possible = find_possible(instruction, start_array, end_array)
|
||||||
|
count_more_three += 1 if possible.keys.size >= 3
|
||||||
|
|
||||||
|
opcode = instruction[0]
|
||||||
|
old_set = possibilities[opcode]
|
||||||
|
if old_set.empty?
|
||||||
|
old_set.concat possible.keys
|
||||||
|
else
|
||||||
|
old_set.each do |it|
|
||||||
|
old_set.delete it unless possible.has_key? it
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
index += 4
|
||||||
|
end
|
||||||
|
puts count_more_three
|
||||||
|
|
||||||
|
sure_possibilities = {} of Int32 => Proc(Array(Int32), Int32, Int32, Int32)
|
||||||
|
while sure_possibilities.keys.size != 16
|
||||||
|
possibilities.each_with_index do |set, index|
|
||||||
|
next unless set.size == 1
|
||||||
|
first = set.first
|
||||||
|
sure_possibilities[index] = OPCODES[first]
|
||||||
|
possibilities.each &.delete(first)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
registers = [0, 0, 0, 0]
|
||||||
|
index = start_index
|
||||||
|
while index < lines.size
|
||||||
|
instruction = lines[index].split(" ").map &.to_i32
|
||||||
|
registers[instruction[3]] = sure_possibilities[instruction[0]].call(registers, instruction[1], instruction[2])
|
||||||
|
index += 1
|
||||||
|
end
|
||||||
|
puts registers[0]
|
||||||
|
# opcodes = {} of Int32 => Proc(Int32, Int32, Int32) # 0 => ->(x : Int32, y : Int32) { x + y }
|
||||||
Reference in New Issue
Block a user