Compare commits
18 Commits
fd51b9dca2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| edfd7a1ecb | |||
| 6ff1d97c91 | |||
| b7518c96a1 | |||
| ca92e7cca0 | |||
| 6eef99e081 | |||
| c96b0c667e | |||
| 453521de55 | |||
| 11483b86ed | |||
| c132a28232 | |||
| b8a8a2155f | |||
| b0e079de0f | |||
| fde761d6af | |||
| 24c93c7a63 | |||
| a24b176417 | |||
| e6a76e428f | |||
| 735170ea73 | |||
| 5ac0c699e5 | |||
| cf06140366 |
44
common.cr
Normal file
44
common.cr
Normal file
@@ -0,0 +1,44 @@
|
||||
module Enumerable(T)
|
||||
def count_each
|
||||
count_map = {} of T => Int32
|
||||
each do |other|
|
||||
count_map[other] = (count_map[other]? || 0) + 1
|
||||
end
|
||||
return count_map
|
||||
end
|
||||
end
|
||||
|
||||
class Rectangle
|
||||
def initialize(@x : Int32, @y : Int32, @width : Int32, @height : Int32)
|
||||
end
|
||||
|
||||
def each_coord(&block)
|
||||
(@x...(@x + @width)).each do |x|
|
||||
(@y...(@y + @height)).each do |y|
|
||||
yield x, y
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class String
|
||||
def select(&block)
|
||||
String.build do |s|
|
||||
each_char do |c|
|
||||
s << c if yield c
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def select!(&block)
|
||||
i = 0
|
||||
s = size
|
||||
while i < s
|
||||
if yield [i]
|
||||
i += 1
|
||||
else
|
||||
s -= 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
day1.cr
Normal file
20
day1.cr
Normal file
@@ -0,0 +1,20 @@
|
||||
changes = File.read("day1_input").split("\n")
|
||||
.select { |it| !it.empty? }
|
||||
.map(&.to_i)
|
||||
|
||||
puts "Final frequency: #{changes.sum}"
|
||||
|
||||
count = { 0 => 1 }
|
||||
acc = 0
|
||||
|
||||
changes.cycle do |i|
|
||||
acc += i
|
||||
old_count = count[acc]? || 0
|
||||
new_count = old_count + 1
|
||||
count[acc] = new_count
|
||||
|
||||
if new_count == 2
|
||||
puts acc
|
||||
exit
|
||||
end
|
||||
end
|
||||
57
day10.cr
Normal file
57
day10.cr
Normal file
@@ -0,0 +1,57 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day10_input").split "\n"
|
||||
lines.pop
|
||||
|
||||
points = [] of Tuple(Int64, Int64, Int64, Int64)
|
||||
|
||||
REGEX = /position=<\s*(-?\d+),\s*(-?\d+)> velocity=<\s*(-?\d+), \s*(-?\d+)>/
|
||||
lines.map(&.match(REGEX)).each do |match|
|
||||
match = match.not_nil!
|
||||
points << { match[1].to_i64, match[2].to_i64, match[3].to_i64, match[4].to_i64 }
|
||||
end
|
||||
|
||||
def max_distance(points)
|
||||
points.product(points).max_of do |left, right|
|
||||
(left[0] - right[0]).abs + (left[1] - right[1]).abs
|
||||
end
|
||||
end
|
||||
|
||||
def simulate(points, multiplier = 1)
|
||||
points.size.times do |index|
|
||||
old = points[index]
|
||||
points[index] = { old[0] + multiplier * old[2], old[1] + multiplier * old[3], old[2], old[3] }
|
||||
end
|
||||
end
|
||||
|
||||
def print_message(points)
|
||||
max_x = points.max_of &.[0]
|
||||
min_x = points.min_of &.[0]
|
||||
max_y = points.max_of &.[1]
|
||||
min_y = points.min_of &.[1]
|
||||
|
||||
(max_y - min_y + 2).times do |yoff|
|
||||
(max_x - min_x + 2).times do |xoff|
|
||||
x = min_x + xoff - 1
|
||||
y = min_y + yoff - 1
|
||||
if points.any? { |point| (point[0] == x) && (point[1] == y) }
|
||||
STDOUT << '▩'
|
||||
else
|
||||
STDOUT << '.'
|
||||
end
|
||||
end
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
d1 = max_distance(points)
|
||||
simulate(points)
|
||||
d2 = max_distance(points)
|
||||
times = (d2 / (d2 - d1)).abs - 10
|
||||
simulate(points, times)
|
||||
|
||||
20.times do |i|
|
||||
simulate(points, multiplier = 1)
|
||||
print_message(points)
|
||||
puts "^ #{times + 1 + i + 1}"
|
||||
end
|
||||
64
day11.cr
Normal file
64
day11.cr
Normal file
@@ -0,0 +1,64 @@
|
||||
map = Array(Array(Array(Int32))).new(301) { |i| Array(Array(Int32)).new(301) { |j| Array(Int32).new(301, 0) } }
|
||||
|
||||
pairs = (1..300).to_a.product (1..300).to_a
|
||||
(1..300).each do |y|
|
||||
(1..300).each do |x|
|
||||
rack_id = x + 10
|
||||
level = rack_id * y
|
||||
level += 3463
|
||||
level *= rack_id
|
||||
digit = (level / 100) % 10
|
||||
digit -= 5
|
||||
|
||||
map[x][y][1] = digit
|
||||
end
|
||||
end
|
||||
|
||||
def square_score(powers, coord, size)
|
||||
x, y = coord
|
||||
total = 0
|
||||
half = size / 2
|
||||
|
||||
total = powers[x][y][half] +
|
||||
powers[x][y + half][half] +
|
||||
powers[x + half][y][half] +
|
||||
powers[x + half][y + half][half]
|
||||
|
||||
if size.odd?
|
||||
size.times do |i|
|
||||
total += powers[x + size - 1][y + i][1]
|
||||
total += powers[x + i][y + size - 1][1]
|
||||
end
|
||||
total -= powers[x + size - 1][y + size - 1][1]
|
||||
end
|
||||
|
||||
powers[x][y][size] = total
|
||||
return total
|
||||
end
|
||||
|
||||
def max_score(powers, pairs, size = 3)
|
||||
possible_squares = (1..300 - size + 1).to_a
|
||||
possible_pairs = possible_squares.product possible_squares
|
||||
|
||||
possible_pairs.max_by do |pair|
|
||||
x, y = pair
|
||||
score = square_score(powers, pair, size)
|
||||
end
|
||||
end
|
||||
|
||||
puts max_score(map, pairs, 2)
|
||||
puts max_score(map, pairs, 3)
|
||||
|
||||
max = -1000
|
||||
max_pair = nil
|
||||
297.times do |i|
|
||||
pair = max_score(map, pairs, i + 4)
|
||||
score = square_score(map, pair, i + 4)
|
||||
if score > max
|
||||
max = score
|
||||
max_pair = { pair, i + 4 }
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
puts max_pair
|
||||
61
day12.cr
Normal file
61
day12.cr
Normal file
@@ -0,0 +1,61 @@
|
||||
require "./common.cr"
|
||||
require "big"
|
||||
|
||||
lines = File.read("day12_input").split "\n"
|
||||
lines.pop
|
||||
|
||||
initial = lines[0].split(": ")[1].chars
|
||||
original = initial.join("")
|
||||
rules = lines[2..-1].map do |it|
|
||||
split = it.split " => "
|
||||
line = split[0]
|
||||
into = split[1][0]
|
||||
{ line.chars, into }
|
||||
end
|
||||
|
||||
class Array
|
||||
def has_plant(i)
|
||||
return false if i < 0 || i >= size
|
||||
return self[i] == '#'
|
||||
end
|
||||
end
|
||||
|
||||
def step(initial, rules)
|
||||
array = [] of Char
|
||||
(-2..initial.size + 2).each do |i|
|
||||
rule = [] of Char
|
||||
(i-2..i+2).each do |ind|
|
||||
rule << (initial.has_plant(ind) ? '#' : '.')
|
||||
end
|
||||
|
||||
new_char = rules.find(&.[0].==(rule)).try(&.[1]) || '.'
|
||||
array << new_char
|
||||
end
|
||||
return array
|
||||
end
|
||||
|
||||
def get_total(initial, offset)
|
||||
total = 0
|
||||
initial.each_with_index do |char, i|
|
||||
total += (char == '#') ? (i + offset) : 0
|
||||
end
|
||||
return total
|
||||
end
|
||||
|
||||
offset = 0
|
||||
|
||||
20.times do |it|
|
||||
initial = step(initial, rules)
|
||||
offset -= 2
|
||||
end
|
||||
puts get_total(initial, offset)
|
||||
|
||||
total = BigInt.new("0")
|
||||
fivebil = BigInt.new("50000000000")
|
||||
first_index = (fivebil - 11)
|
||||
|
||||
total += first_index * 57
|
||||
total += get_total("###.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#".chars, 0)
|
||||
puts total
|
||||
|
||||
# 2850000002508
|
||||
120
day13.cr
Normal file
120
day13.cr
Normal file
@@ -0,0 +1,120 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day13_input").split "\n"
|
||||
lines.pop
|
||||
|
||||
map = lines.map &.chars
|
||||
carts = [] of Tuple(Int32, Int32, Char, Char)
|
||||
|
||||
map.each_with_index do |line, y|
|
||||
line.each_with_index do |char, x|
|
||||
carts << {x, y, char, 'l'} if char == '>' || char == '<' || char == '^' || char == 'v'
|
||||
end
|
||||
end
|
||||
|
||||
def next_turn(char)
|
||||
return 'r' if char == 's'
|
||||
return 'l' if char == 'r'
|
||||
return 's'
|
||||
end
|
||||
|
||||
def turn_dir(char, turn)
|
||||
if turn == 'l'
|
||||
return '<' if char == '^'
|
||||
return 'v' if char == '<'
|
||||
return '>' if char == 'v'
|
||||
return '^' if char == '>'
|
||||
end
|
||||
if turn == 'r'
|
||||
return '>' if char == '^'
|
||||
return '^' if char == '<'
|
||||
return '<' if char == 'v'
|
||||
return 'v' if char == '>'
|
||||
end
|
||||
return char
|
||||
end
|
||||
|
||||
def offset(char)
|
||||
case char
|
||||
when '^'
|
||||
{0, -1}
|
||||
when 'v'
|
||||
{0, 1}
|
||||
when '<'
|
||||
{-1, 0}
|
||||
when '>'
|
||||
{1, 0}
|
||||
else
|
||||
{0, 0}
|
||||
end
|
||||
end
|
||||
|
||||
def turn(cart, turnchar)
|
||||
if turnchar == '/'
|
||||
return '>' if cart == '^'
|
||||
return 'v' if cart == '<'
|
||||
return '^' if cart == '>'
|
||||
return '<'
|
||||
elsif turnchar == '\\'
|
||||
return 'v' if cart == '>'
|
||||
return '<' if cart == '^'
|
||||
return '^' if cart == '<'
|
||||
return '>'
|
||||
end
|
||||
return cart
|
||||
end
|
||||
|
||||
def simulate(map, carts)
|
||||
crashed = [] of Int32
|
||||
crashed_pos = [] of Tuple(Int32, Int32)
|
||||
|
||||
carts.sort_by! do |cart|
|
||||
x, y, c, t = cart
|
||||
y * 280 + x
|
||||
end
|
||||
|
||||
carts.each_with_index do |cart, index|
|
||||
next if crashed.includes? index
|
||||
x, y, c, t = cart
|
||||
|
||||
# TRUSTED
|
||||
dx, dy = offset(c)
|
||||
x += dx
|
||||
y += dy
|
||||
if map[y][x] == '+'
|
||||
c = turn_dir(c, t)
|
||||
t = next_turn(t)
|
||||
else
|
||||
c = turn(c, map[y][x])
|
||||
end
|
||||
# UNTRUSTED
|
||||
|
||||
carts.each_with_index do |c2, i2|
|
||||
next if crashed.includes? i2
|
||||
next if (c2[0] != x) || (c2[1] != y)
|
||||
|
||||
puts carts
|
||||
crashed << index
|
||||
crashed << i2
|
||||
crashed_pos << {x, y}
|
||||
end
|
||||
|
||||
carts[index] = {x, y, c, t}
|
||||
end
|
||||
|
||||
crashed.sort!.reverse!.uniq!.each do |index|
|
||||
carts.delete_at index
|
||||
end
|
||||
|
||||
unless crashed_pos.empty?
|
||||
puts crashed_pos
|
||||
end
|
||||
|
||||
return carts.size <= 1
|
||||
end
|
||||
|
||||
count = 0
|
||||
while !simulate(map, carts)
|
||||
count += 1
|
||||
end
|
||||
puts carts[0]?
|
||||
48
day14.cr
Normal file
48
day14.cr
Normal file
@@ -0,0 +1,48 @@
|
||||
require "./common.cr"
|
||||
|
||||
LINE_COUNT = 637061
|
||||
recipes = Deque(Int32).new
|
||||
recipes << 3
|
||||
recipes << 7
|
||||
elves = [0, 1]
|
||||
|
||||
def add_recipes(recipes, elves)
|
||||
new_num = elves.map { |it| recipes[it] }.sum
|
||||
new_num.to_s.chars.map(&.to_i32).each do |n|
|
||||
recipes << n
|
||||
end
|
||||
|
||||
elves.each_with_index do |elf, index|
|
||||
elves[index] = (elf + recipes[elf] + 1) % recipes.size
|
||||
end
|
||||
end
|
||||
|
||||
def index?(recipes, sequence, start_at = 0)
|
||||
return nil if recipes.size < sequence.size
|
||||
(recipes.size-sequence.size+1-start_at).times do |offset|
|
||||
correct = true
|
||||
sequence.each_with_index do |s, i|
|
||||
correct &= (recipes[start_at + offset + i] == s)
|
||||
break unless correct
|
||||
end
|
||||
return start_at + offset if correct
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# Part 1
|
||||
while recipes.size < LINE_COUNT + 10
|
||||
add_recipes(recipes, elves)
|
||||
end
|
||||
|
||||
puts recipes.to_a[-10..-1].map(&.to_s).join
|
||||
|
||||
# Part 2
|
||||
digits = LINE_COUNT.to_s.chars.map(&.to_i32)
|
||||
index = nil
|
||||
old_size = 0
|
||||
until (index = index?(recipes, digits, Math.max(old_size - 10, 0)))
|
||||
old_size = recipes.size
|
||||
add_recipes(recipes, elves)
|
||||
end
|
||||
puts index
|
||||
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 }
|
||||
@@ -1,4 +0,0 @@
|
||||
puts File.read("day1").split("\n")
|
||||
.select { |it| !it.empty? }
|
||||
.map(&.to_i)
|
||||
.sum
|
||||
19
day1_2.cr
19
day1_2.cr
@@ -1,19 +0,0 @@
|
||||
count = { 0 => 1 }
|
||||
acc = 0
|
||||
changes = File.read("day1").split("\n")
|
||||
.select { |it| !it.empty? }
|
||||
.map(&.to_i)
|
||||
|
||||
while true
|
||||
changes.each do |i|
|
||||
acc += i
|
||||
old_count = count[acc]? || 0
|
||||
new_count = old_count + 1
|
||||
count[acc] = new_count
|
||||
|
||||
if new_count == 2
|
||||
puts acc
|
||||
exit
|
||||
end
|
||||
end
|
||||
end
|
||||
30
day2.cr
Normal file
30
day2.cr
Normal file
@@ -0,0 +1,30 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day2_input").split("\n")
|
||||
lines.pop
|
||||
|
||||
CHARS = ('a'..'z').to_a
|
||||
|
||||
def has_n?(string, n)
|
||||
return string.chars.count_each(CHARS).any?(&.[](1).==(n))
|
||||
end
|
||||
|
||||
def count_changes(s1, s2)
|
||||
pairs = s1.chars.zip s2.chars
|
||||
total = 0
|
||||
pairs.each do |pair|
|
||||
total += 1 if pair[0] != pair[1]
|
||||
end
|
||||
return total
|
||||
end
|
||||
|
||||
two = lines.count { |s| has_n?(s, 2) }
|
||||
three = lines.count { |s| has_n?(s, 3) }
|
||||
puts "\"Hash\": #{two * three}"
|
||||
|
||||
lines.product(lines).each do |pair|
|
||||
if count_changes(pair[0], pair[1]) == 1
|
||||
puts "Strings: #{pair[0]}, #{pair[1]}"
|
||||
exit
|
||||
end
|
||||
end
|
||||
15
day2_1.cr
15
day2_1.cr
@@ -1,15 +0,0 @@
|
||||
lines = File.read("day2").split("\n")
|
||||
lines.pop
|
||||
|
||||
CHARS = ('a'..'z').to_a
|
||||
|
||||
def has_n?(string, n)
|
||||
CHARS.each do |c|
|
||||
return true if string.count(c) == n
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
two = lines.count { |s| has_n?(s, 2) }
|
||||
three = lines.count { |s| has_n?(s, 3) }
|
||||
puts two * three
|
||||
15
day2_2.cr
15
day2_2.cr
@@ -1,15 +0,0 @@
|
||||
lines = File.read("day2").split("\n")
|
||||
lines.pop
|
||||
|
||||
def count_changes(s1, s2)
|
||||
pairs = s1.chars.zip s2.chars
|
||||
total = 0
|
||||
pairs.each do |pair|
|
||||
total += 1 if pair[0] != pair[1]
|
||||
end
|
||||
return total
|
||||
end
|
||||
|
||||
lines.product(lines).each do |pair|
|
||||
puts "#{pair[0]}, #{pair[1]}" if count_changes(pair[0], pair[1]) == 1
|
||||
end
|
||||
38
day3.cr
Normal file
38
day3.cr
Normal file
@@ -0,0 +1,38 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day3_input").split("\n")
|
||||
lines.pop
|
||||
|
||||
REGEX = /#([0-9]+) @ ([0-9]+),([0-9]+): ([0-9]+)x([0-9]+)/
|
||||
|
||||
claims = {} of Int32 => Rectangle
|
||||
lines.each do |line|
|
||||
match = line.match(REGEX).not_nil!
|
||||
claim = match[1].to_i32
|
||||
x = match[2].to_i32
|
||||
y = match[3].to_i32
|
||||
width = match[4].to_i32
|
||||
height = match[5].to_i32
|
||||
claims[claim] = Rectangle.new(x, y, width, height)
|
||||
end
|
||||
|
||||
claimed_coords = {} of Tuple(Int32, Int32) => Int32
|
||||
claims.values.each do |claim|
|
||||
claim.each_coord do |x, y|
|
||||
coord = {x, y}
|
||||
claimed_coords[coord] = (claimed_coords[coord]? || 0) + 1
|
||||
end
|
||||
end
|
||||
|
||||
puts "Overlapping: #{claimed_coords.values.count &.>(1)}"
|
||||
|
||||
claims.each do |k, v|
|
||||
count_overlapped = 0
|
||||
v.each_coord do |x, y|
|
||||
count_overlapped += 1 if claimed_coords[{x, y}] > 1
|
||||
end
|
||||
if count_overlapped == 0
|
||||
puts "No overlaps: #{k}"
|
||||
exit
|
||||
end
|
||||
end
|
||||
52
day4.cr
Normal file
52
day4.cr
Normal file
@@ -0,0 +1,52 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day4_input").split("\n")
|
||||
lines.pop
|
||||
lines.sort!
|
||||
|
||||
REGEX = /\[(\d+)-(\d+)-(\d+) (\d+):(\d+)\].+/
|
||||
GUARD_REGEX = /.+#(\d+).+/
|
||||
|
||||
active = 0
|
||||
last_time = {0, 1, 0, 0, 0}
|
||||
minutes = {} of Int32 => Array(Int32)
|
||||
|
||||
lines.each do |line|
|
||||
match = line.match(REGEX).not_nil!
|
||||
year = match[1].to_i32
|
||||
month = match[2].to_i32
|
||||
day = match[3].to_i32
|
||||
hour = match[4].to_i32
|
||||
minute = match[5].to_i32
|
||||
new_time = {year, month, day, hour, minute}
|
||||
|
||||
if line.includes? "Guard"
|
||||
active = line.match(GUARD_REGEX).not_nil![1].to_i32
|
||||
elsif line.includes? "wakes"
|
||||
old_year, old_month, old_day, old_hour, old_minute = last_time
|
||||
difference = (day - old_day) * 24 * 60 +
|
||||
(hour - old_hour) * 60 +
|
||||
(minute - old_minute)
|
||||
|
||||
array = minutes[active]? || Array.new(60, 0)
|
||||
60.times do |m|
|
||||
array[m] += difference / 60
|
||||
end
|
||||
(difference % 60).times do |m|
|
||||
actual_minute = (old_minute + m) % 60
|
||||
array[actual_minute] += 1
|
||||
end
|
||||
minutes[active] = array
|
||||
end
|
||||
last_time = new_time
|
||||
end
|
||||
|
||||
guard_num, guard_minutes = minutes.max_by &.[1].sum
|
||||
puts "Guard num: #{guard_num}"
|
||||
puts "Guard minute: #{(guard_minutes.index guard_minutes.max).not_nil!}"
|
||||
puts "Answer num: #{guard_num * (guard_minutes.index guard_minutes.max).not_nil!}"
|
||||
|
||||
guard_num, guard_minutes = minutes.max_by &.[1].max
|
||||
puts "Guard num: #{guard_num}"
|
||||
puts "Guard minute: #{(guard_minutes.index guard_minutes.max).not_nil!}"
|
||||
puts "Answer num: #{guard_num * (guard_minutes.index guard_minutes.max).not_nil!}"
|
||||
34
day5.cr
Normal file
34
day5.cr
Normal file
@@ -0,0 +1,34 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day5_input").split("\n")
|
||||
|
||||
struct Char
|
||||
def inverse?(other)
|
||||
return (other != self) && (other.downcase == self.downcase)
|
||||
end
|
||||
end
|
||||
|
||||
def react(string)
|
||||
stack = [] of Char
|
||||
|
||||
string.each_char do |char|
|
||||
last_char = stack.last?
|
||||
if !last_char || !last_char.inverse? char
|
||||
stack << char
|
||||
else
|
||||
stack.pop
|
||||
end
|
||||
end
|
||||
|
||||
return stack
|
||||
end
|
||||
|
||||
puts react(lines[0]).size
|
||||
|
||||
pairs = {} of Tuple(Char, Char) => Int32
|
||||
('a'..'z').to_a.zip(('A'..'Z').to_a).each do |pair|
|
||||
new_string = lines[0].select { |c| c != pair[0] && c != pair[1] }
|
||||
pairs[pair] = react(new_string).size
|
||||
end
|
||||
|
||||
puts pairs.values.min
|
||||
65
day6.cr
Normal file
65
day6.cr
Normal file
@@ -0,0 +1,65 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day6_input").split("\n")
|
||||
lines.pop
|
||||
|
||||
REGEX = /(\d+), (\d+)/
|
||||
coords = lines.map(&.match(REGEX))
|
||||
.select { |it| it != nil }
|
||||
.map { |match| { match.not_nil![1].to_i32, match.not_nil![2].to_i32 } }
|
||||
|
||||
def distance(a, b)
|
||||
(a[0] - b[0]).abs + (a[1] - b[1]).abs
|
||||
end
|
||||
|
||||
def compute_area(coords, coord)
|
||||
visited = Set(Tuple(Int32, Int32)).new
|
||||
total = 0
|
||||
|
||||
coord_queue = Set(Tuple(Int32, Int32)).new
|
||||
coord_queue << coord
|
||||
|
||||
while !coord_queue.empty?
|
||||
new_coord = coord_queue.first
|
||||
coord_queue.delete new_coord
|
||||
|
||||
next if visited.includes? new_coord
|
||||
visited << new_coord
|
||||
|
||||
distances = coords.group_by do |coord|
|
||||
distance(coord, new_coord)
|
||||
end
|
||||
min_distance = distances.keys.min
|
||||
|
||||
next if distances[min_distance].size != 1
|
||||
next if distances[min_distance][0] != coord
|
||||
|
||||
total += 1
|
||||
x, y = new_coord
|
||||
coord_queue << { x + 1, y }
|
||||
coord_queue << { x - 1, y }
|
||||
coord_queue << { x, y + 1 }
|
||||
coord_queue << { x, y - 1 }
|
||||
|
||||
if total > 10000
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
return total
|
||||
end
|
||||
|
||||
def find_region(coords)
|
||||
total = 0
|
||||
(-11000/2..11000/2).each do |x|
|
||||
(-11000/2..11000/2).each do |y|
|
||||
coord = {x, y}
|
||||
total += 1 if coords.sum { |it| distance(it, coord) } < 10000
|
||||
end
|
||||
end
|
||||
|
||||
return total
|
||||
end
|
||||
|
||||
puts coords.max_of { |it| compute_area(coords, it) }
|
||||
puts find_region(coords)
|
||||
87
day7.cr
Normal file
87
day7.cr
Normal file
@@ -0,0 +1,87 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day7_input").split("\n")
|
||||
lines.pop
|
||||
|
||||
REGEX = /Step (.+) must be finished before step (.+) can begin./
|
||||
|
||||
all_children = {} of String => Set(String)
|
||||
all = Set(String).new
|
||||
finished = Set(String).new
|
||||
finished_array = Array(String).new
|
||||
|
||||
lines.map(&.match(REGEX)).each do |match|
|
||||
match = match.not_nil!
|
||||
child = match[1]
|
||||
parent = match[2]
|
||||
|
||||
all << child
|
||||
all << parent
|
||||
parent_set = all_children[parent]? || Set(String).new
|
||||
parent_set << child
|
||||
|
||||
all_children[parent] = parent_set
|
||||
end
|
||||
|
||||
available_at = {} of String => Int32
|
||||
|
||||
class String
|
||||
def cost
|
||||
return (self[0].bytes[0] - 'A'.bytes[0]).to_i32 + 1
|
||||
end
|
||||
end
|
||||
|
||||
def get_available(all, finished, children)
|
||||
available = all.select do |it|
|
||||
next false if finished.includes? it
|
||||
next true unless set = children[it]?
|
||||
next true if set.size == 0
|
||||
end
|
||||
end
|
||||
|
||||
children = all_children.clone
|
||||
while finished.size != all.size
|
||||
available = get_available(all, finished, children)
|
||||
available.sort!
|
||||
|
||||
first = available.first
|
||||
finished << first
|
||||
finished_array << first
|
||||
|
||||
children.values.each do |value|
|
||||
value.delete first
|
||||
end
|
||||
end
|
||||
puts finished_array.join ""
|
||||
|
||||
finished.clear
|
||||
finished_array.clear
|
||||
children = all_children.clone
|
||||
workers = [0, 0, 0, 0, 0]
|
||||
current_time = 0
|
||||
finished_at = {} of String => Int32
|
||||
|
||||
while finished.size < all.size
|
||||
workers.each_with_index do |worker, index|
|
||||
next if worker > current_time
|
||||
|
||||
current_finished = finished_at.select { |k, v| v <= current_time }.map(&.[0]).to_set
|
||||
available = all.select do |it|
|
||||
next false if finished.includes? it
|
||||
next true unless nodes = children[it]?
|
||||
(nodes - current_finished).size == 0
|
||||
end
|
||||
|
||||
available = available.sort!
|
||||
next if available.empty?
|
||||
|
||||
first = available.first
|
||||
finished_at[first] = current_time + first.cost + 60
|
||||
workers[index] = current_time + first.cost + 60
|
||||
finished_array << first
|
||||
finished << first
|
||||
end
|
||||
|
||||
current_time += 1
|
||||
end
|
||||
puts finished_at.values.max
|
||||
49
day8.cr
Normal file
49
day8.cr
Normal file
@@ -0,0 +1,49 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day8_input").split("\n")
|
||||
lines.pop
|
||||
|
||||
class Node
|
||||
getter children : Array(Node)
|
||||
getter metadata : Array(Int32)
|
||||
|
||||
def initialize(@children : Array(Node), @metadata : Array(Int32))
|
||||
end
|
||||
|
||||
def sum
|
||||
return metadata.sum + children.map(&.sum.as Int32).sum
|
||||
end
|
||||
|
||||
def value
|
||||
return metadata.sum if children.empty?
|
||||
metadata.map do |meta|
|
||||
next 0 unless child = children[meta - 1]?
|
||||
next child.value
|
||||
end.sum
|
||||
end
|
||||
end
|
||||
|
||||
def parse(numbers, index = 0)
|
||||
child_count = numbers[index]
|
||||
index += 1
|
||||
meta_count = numbers[index]
|
||||
index += 1
|
||||
|
||||
|
||||
children = Array(Node).new
|
||||
child_count.times do |count|
|
||||
child, index = parse(numbers, index)
|
||||
children << child
|
||||
end
|
||||
|
||||
meta = numbers[index...(index + meta_count)]
|
||||
index += meta_count
|
||||
|
||||
node = Node.new children, meta
|
||||
return {node, index}
|
||||
end
|
||||
|
||||
numbers = lines[0].split(" ").map &.to_i32
|
||||
tree = parse(numbers)
|
||||
puts tree[0].sum
|
||||
puts tree[0].value
|
||||
36
day9.cr
Normal file
36
day9.cr
Normal file
@@ -0,0 +1,36 @@
|
||||
require "./common.cr"
|
||||
|
||||
lines = File.read("day9_input").split "\n"
|
||||
lines.pop
|
||||
|
||||
players = 463
|
||||
last_points = 400
|
||||
|
||||
marbles = [ 0 ]
|
||||
scores = Array(Int32).new(players, 0)
|
||||
player = 0
|
||||
pos = 0
|
||||
|
||||
(last_points).times do |i|
|
||||
i = i + 1
|
||||
if (i % 23 == 0)
|
||||
delete_at = (pos - 7 - marbles.size) % marbles.size
|
||||
|
||||
puts "current: #{i}"
|
||||
puts "popping: #{marbles[delete_at]}"
|
||||
scores[player] += i
|
||||
scores[player] += marbles[delete_at]
|
||||
|
||||
pos = delete_at
|
||||
marbles.delete_at delete_at
|
||||
else
|
||||
at = (pos + 2) % marbles.size
|
||||
marbles.insert at, i
|
||||
pos = marbles.index(i).not_nil!
|
||||
end
|
||||
|
||||
puts marbles
|
||||
player = (player + 1) % players
|
||||
end
|
||||
|
||||
puts scores.max
|
||||
Reference in New Issue
Block a user