You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							149 lines
						
					
					
						
							3.8 KiB
						
					
					
				
			
		
		
	
	
							149 lines
						
					
					
						
							3.8 KiB
						
					
					
				#!/usr/bin/env julia
 | 
						|
 | 
						|
using Test
 | 
						|
using Images
 | 
						|
 | 
						|
struct Move
 | 
						|
  direction :: Char
 | 
						|
  count :: Int
 | 
						|
end
 | 
						|
 | 
						|
mutable struct Point{T}
 | 
						|
  x::T
 | 
						|
  y::T
 | 
						|
end
 | 
						|
 | 
						|
function parse_move(line)
 | 
						|
  parts = split(line, limit=2)
 | 
						|
  return Move(parts[1][1], parse(Int, parts[2]))
 | 
						|
end
 | 
						|
 | 
						|
function read_moves(io)
 | 
						|
  return [parse_move(line) for line in readlines(io)]
 | 
						|
end
 | 
						|
 | 
						|
function apply_move(head, tail, direction)
 | 
						|
  if direction == 'U'
 | 
						|
    head.y += 1
 | 
						|
  elseif direction == 'D'
 | 
						|
    head.y -= 1
 | 
						|
  elseif direction == 'L'
 | 
						|
    head.x -= 1
 | 
						|
  elseif direction == 'R'
 | 
						|
    head.x += 1
 | 
						|
  elseif direction != 'O'
 | 
						|
    throw(DomainError("Unknown direction"))
 | 
						|
  end
 | 
						|
  x_diff = head.x - tail.x
 | 
						|
  y_diff = head.y - tail.y
 | 
						|
  if abs(x_diff) <= 1 && abs(y_diff) <= 1
 | 
						|
    return head, tail
 | 
						|
  end
 | 
						|
  if abs(x_diff) <= 1
 | 
						|
    tail.x += x_diff
 | 
						|
    tail.y += sign(y_diff) * (abs(y_diff) - 1)
 | 
						|
  elseif abs(y_diff) <= 1
 | 
						|
    tail.y += y_diff
 | 
						|
    tail.x += sign(x_diff) * (abs(x_diff) - 1)
 | 
						|
  elseif abs(x_diff) == 2 && abs(y_diff) == 2
 | 
						|
    # possible with more than 2 knots
 | 
						|
    tail.x += sign(x_diff) * (abs(x_diff) - 1)
 | 
						|
    tail.y += sign(y_diff) * (abs(y_diff) - 1)
 | 
						|
  else
 | 
						|
    throw(DomainError("invalid knots " * string(head) * " " * string(tail)))
 | 
						|
  end 
 | 
						|
  return head, tail
 | 
						|
end
 | 
						|
 | 
						|
function get_visited(moves; nknots::Int=2)
 | 
						|
  visited = Set{NTuple{2, Int}}()
 | 
						|
  knots = [Point(0, 0) for _ in 1:nknots]
 | 
						|
  push!(visited, (knots[nknots].x, knots[nknots].y))
 | 
						|
  for move in moves
 | 
						|
    for _ in 1:move.count
 | 
						|
      knots[1], knots[2] = apply_move(knots[1], knots[2], move.direction)
 | 
						|
      for i in 2:nknots
 | 
						|
        _, knots[i] = apply_move(knots[i-1], knots[i], 'O')
 | 
						|
      end
 | 
						|
      push!(visited, (knots[nknots].x, knots[nknots].y))
 | 
						|
    end
 | 
						|
  end
 | 
						|
  return visited
 | 
						|
end
 | 
						|
 | 
						|
function int_map(visited)
 | 
						|
  (min_x, max_x) = extrema([t[1] for t in visited])
 | 
						|
  (min_y, max_y) = extrema([t[2] for t in visited])
 | 
						|
  nrows = max_y - min_y + 1
 | 
						|
  ncols = max_x - min_x + 1
 | 
						|
  imap = zeros(Int, nrows, ncols)
 | 
						|
  for (x, y) in visited
 | 
						|
    imap[nrows - (y - min_y), (x - min_x + 1)] = 1
 | 
						|
  end
 | 
						|
  imap[nrows + min_y, 1 - min_x] = -1
 | 
						|
  return imap
 | 
						|
end
 | 
						|
 | 
						|
function ascii_map(visited)
 | 
						|
  map_char(n) = (n == -1 ? 's'
 | 
						|
                 : (n == 1 ? "#"[1] : '.'))
 | 
						|
  srows = mapreduce(map_char, *, int_map(visited), dims=2; init="")
 | 
						|
  return join(srows, "\n")
 | 
						|
end
 | 
						|
 | 
						|
function scale_pixels(img, scale::Int)
 | 
						|
  (nrows, ncols) = size(img)
 | 
						|
  new_img = Matrix{RGB}(undef, nrows * scale, ncols * scale)
 | 
						|
  for i in 1:nrows
 | 
						|
    for j in 1:ncols
 | 
						|
      i2 = (i - 1) * scale + 1
 | 
						|
      j2 = (j - 1) * scale + 1
 | 
						|
      new_img[i2:i2+scale-1, j2:j2+scale-1] .= img[i, j]
 | 
						|
    end
 | 
						|
  end
 | 
						|
  return new_img
 | 
						|
end
 | 
						|
 | 
						|
function image_map(visited; scale::Int=10)
 | 
						|
  imap = int_map(visited)
 | 
						|
  map_color(n) = (n == -1 ? RGB(0, 0.8, 0)
 | 
						|
                  : (n == 1 ? RGB(0.2, 0.2, 0.0) : RGB(0, 0, 0)))
 | 
						|
  cmap = map(map_color, int_map(visited))
 | 
						|
  return scale_pixels(cmap, scale)
 | 
						|
end
 | 
						|
 | 
						|
function test()
 | 
						|
  @testset "visited count 2 knot" verbose=true begin
 | 
						|
    @test length(get_visited(read_moves("example.txt"))) == 13
 | 
						|
    @test length(get_visited(read_moves("input.txt"))) == 6037
 | 
						|
  end
 | 
						|
  @testset "visited count 10 knot" verbose=true begin
 | 
						|
    @test length(get_visited(read_moves("example.txt"), nknots=10)) == 1
 | 
						|
    @test length(get_visited(read_moves("input.txt"), nknots=10)) == 2485
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
function main()
 | 
						|
  if size(ARGS, 1) == 0
 | 
						|
    test()
 | 
						|
  else
 | 
						|
    infile = ARGS[1]
 | 
						|
    println("infile = ", infile)
 | 
						|
    moves = read_moves(infile)
 | 
						|
    println("moves: ", moves)
 | 
						|
    visited = get_visited(moves)
 | 
						|
    println("visited: ", visited)
 | 
						|
    println("count  : ", length(visited))
 | 
						|
    println(ascii_map(visited))
 | 
						|
    save("visited.png", image_map(visited))
 | 
						|
 | 
						|
    visited10 = get_visited(moves, nknots=10)
 | 
						|
    println("visited10: ", visited10)
 | 
						|
    println("count10  : ", length(visited10))
 | 
						|
    println(ascii_map(visited10))
 | 
						|
    save("visited10.png", image_map(visited10))
 | 
						|
  end
 | 
						|
end
 | 
						|
 | 
						|
main()
 |