input = new ArrayDeque<>(2);
+ public RetentionPolicy policy;
+ private long[] program;
+ private int instructionCounter = 0;
+ private long lastInput;
+ private long relativeBase = 0;
+
+ public IntcodeComputer(RetentionPolicy policy, long[] program, long... input) {
+ super(0);
+ this.program = Arrays.copyOf(program, 10000);
+ setInput(input);
+ this.policy = policy;
+ }
+
+ public IntcodeComputer(int day, long... input) {
+ super(day);
+ this.program = dayNumbers(",");
+ this.program = Arrays.copyOf(this.program, 10000);
+ if (day == 2) {
+ this.program[1] = input[0];
+ this.program[2] = input[1];
+ } else if (day == 17) {
+ this.program[0] = input[0];
+ }
+ setInput(input);
+ this.policy = RetentionPolicy.EXIT_ON_OUTPUT;
+ }
+
+ public long run(long... input) {
+ setInput(input);
+ return run();
+ }
+
+ public long run() {
+ long result;
+ while ((result = executeInstruction(Math.toIntExact(program[instructionCounter]))) == CONTINUE_CODE) ;
+ return result;
+ }
+
+ private long executeInstruction(int instruction) {
+ if (instruction > 99)
+ return parseComplexInstruction(instruction);
+ return execute(instruction);
+ }
+
+ private long execute(int instruction) {
+ return execute(new int[3], instruction);
+ }
+
+ private long execute(int[] method, int instruction) {
+ int nParams = nParams(instruction);
+ long[] args = IntStream.range(1, nParams + 1).mapToLong(j -> program[instructionCounter + j]).toArray();
+ transformParameters(method, args, instruction);
+ return executeInstruction(args, instruction);
+ }
+
+ private void transformParameters(int[] method, long[] args, int instruction) {
+ IntStream.range(0, args.length).filter(i -> method[i] != 1).filter(i -> i + 1 != args.length || !Arrays.stream(DO_NOT_TRANSFORM_FINAL_ARGUMENT).anyMatch(j -> j == instruction))
+ .forEach(i -> args[i] = program[Math.toIntExact((method[i] == 2 ? relativeBase : 0) + args[i])]);
+ if (Arrays.stream(DO_NOT_TRANSFORM_FINAL_ARGUMENT).anyMatch(j -> j == instruction) && method[args.length - 1] == 2) {
+ args[args.length - 1] += relativeBase;
+ }
+ }
+
+ private long readInput() {
+ if (input.isEmpty())
+ return lastInput;
+ lastInput = input.poll();
+ return lastInput;
+ }
+
+ public void addInput(long... num) {
+ for (long n : num)
+ input.add(n);
+ }
+
+ public long firstElement() {
+ return program[0];
+ }
+
+ public void setInput(long... input) {
+ this.input.clear();
+ addInput(input);
+ }
+
+ private long executeInstruction(long[] args, int instruction) {
+ if (instruction == 3 && policy == RetentionPolicy.EXIT_ON_EMPTY_INPUT && input.size() == 0)
+ return STOP_CODE;
+ instructionCounter += nParams(instruction) + 1;
+ switch (instruction) {
+ case 1:
+ program[Math.toIntExact(args[2])] = args[0] + args[1];
+ break;
+ case 2:
+ program[Math.toIntExact(args[2])] = args[0] * args[1];
+ break;
+ case 3:
+ program[Math.toIntExact(args[0])] = readInput();
+ break;
+ case 4:
+ return args[0];
+ case 5:
+ if (args[0] != 0) instructionCounter = Math.toIntExact(args[1]);
+ break;
+ case 6:
+ if (args[0] == 0) instructionCounter = Math.toIntExact(args[1]);
+ break;
+ case 7:
+ program[Math.toIntExact(args[2])] = args[0] < args[1] ? 1 : 0;
+ break;
+ case 8:
+ program[Math.toIntExact(args[2])] = args[0] == args[1] ? 1 : 0;
+ break;
+ case 9:
+ relativeBase += Math.toIntExact(args[0]);
+ break;
+ case 99:
+ return STOP_CODE;
+ default:
+ throw new IllegalStateException("Something went wrong!");
+ }
+ return CONTINUE_CODE;
+ }
+
+ private long parseComplexInstruction(int instruction) {
+ int[] instructions = getInstructions(instruction);
+ int opcode = getOpCode(instructions);
+ return execute(new int[]{instructions[2], instructions[1], instructions[0]}, opcode);
+ }
+
+ private int getOpCode(int instruction) {
+ return getOpCode(getInstructions(instruction));
+ }
+
+ private int getOpCode(int[] instructions) {
+ return (instructions[3] * 10) + instructions[4];
+ }
+
+ private int[] getInstructions(int instruction) {
+ int[] instructions = new int[5];
+ for (int j = instructions.length - 1; instruction > 0; j--) {
+ instructions[j] = instruction % 10;
+ instruction /= 10;
+ }
+ return instructions;
+ }
+
+ private int nParams(int instruction) {
+ switch (instruction) {
+ case 99:
+ return -1;
+ case 3:
+ case 4:
+ case 9:
+ return 1;
+ case 5:
+ case 6:
+ return 2;
+ case 1:
+ case 2:
+ case 7:
+ case 8:
+ return 3;
+ default:
+ if (instruction > 99) return nParams(getOpCode(instruction));
+ else throw new IllegalStateException("Something went wrong! " + instruction);
+ }
+ }
+
+ public void setElement(int i, long j) {
+ program[i] = j;
+ }
+
+ public void setInput(String patterns) {
+ setInput(patterns.chars().mapToLong(e -> e).toArray());
+ }
+
+ @Override
+ public Object part1() {
+ return null;
+ }
+
+ @Override
+ public Object part2() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/intcode/RetentionPolicy.java b/src/main/java/com/sbaars/adventofcode/year19/intcode/RetentionPolicy.java
new file mode 100644
index 00000000..719d0b1f
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/intcode/RetentionPolicy.java
@@ -0,0 +1,5 @@
+package com.sbaars.adventofcode.year19.intcode;
+
+public enum RetentionPolicy {
+ EXIT_ON_EMPTY_INPUT, EXIT_ON_OUTPUT
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/pathfinding/CharGrid2d.java b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/CharGrid2d.java
new file mode 100644
index 00000000..56706ad7
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/CharGrid2d.java
@@ -0,0 +1,129 @@
+package com.sbaars.adventofcode.year19.pathfinding;
+
+import java.awt.*;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Creates nodes and neighbours from a 2d grid. Each point in the map has an
+ * integer value that specifies the cost of crossing that point. If this value
+ * is negative, the point is unreachable.
+ *
+ * If diagonal movement is allowed, the Chebyshev distance is used, else
+ * Manhattan distance is used.
+ *
+ * @author Ben Ruijl
+ */
+public class CharGrid2d {
+ private final char[][] map;
+ private final boolean allowDiagonal;
+ List collectedKeys;
+
+ public CharGrid2d(char[][] map, boolean allowDiagonal) {
+ this.map = map;
+ this.allowDiagonal = allowDiagonal;
+ }
+
+ public List findPath(Point start, Point end) {
+ return PathFinding.doAStar(new MapNode(start.x, start.y), new MapNode(end.x, end.y)).stream().map(MapNode::toPoint).collect(Collectors.toList());
+ }
+
+ /**
+ * A node in a 2d map. This is simply the coordinates of the point.
+ *
+ * @author Ben Ruijl
+ */
+ public class MapNode implements Node {
+ private final int x, y;
+
+ public MapNode(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public double getHeuristic(MapNode goal) {
+ if (allowDiagonal) {
+ return Math.max(Math.abs(x - goal.x), Math.abs(y - goal.y));
+ } else {
+ return Math.abs(x - goal.x) + Math.abs(y - goal.y);
+ }
+ }
+
+ public double getTraversalCost(MapNode neighbour) {
+ return 1 + map[neighbour.y][neighbour.x];
+ }
+
+ public Set getNeighbours() {
+ Set neighbours = new HashSet();
+
+ for (int i = x - 1; i <= x + 1; i++) {
+ for (int j = y - 1; j <= y + 1; j++) {
+ if ((i == x && j == y) || i < 0 || j < 0 || j >= map.length
+ || i >= map[j].length) {
+ continue;
+ }
+
+ if (!allowDiagonal &&
+ ((i < x && j < y) ||
+ (i < x && j > y) ||
+ (i > x && j > y) ||
+ (i > x && j < y))) {
+ continue;
+ }
+
+ if (map[j][i] == '#') {
+ continue;
+ }
+
+ // TODO: create cache instead of recreation
+ neighbours.add(new MapNode(i, j));
+ }
+ }
+
+ return neighbours;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + x + ", " + y + ")";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getOuterType().hashCode();
+ result = prime * result + x;
+ result = prime * result + y;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MapNode other = (MapNode) obj;
+ if (!getOuterType().equals(other.getOuterType()))
+ return false;
+ if (x != other.x)
+ return false;
+ return y == other.y;
+ }
+
+ public Point toPoint() {
+ return new Point(x, y);
+ }
+
+ private CharGrid2d getOuterType() {
+ return CharGrid2d.this;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Grid2d.java b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Grid2d.java
new file mode 100644
index 00000000..b3d1b666
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Grid2d.java
@@ -0,0 +1,129 @@
+package com.sbaars.adventofcode.year19.pathfinding;
+
+import com.sbaars.adventofcode.year19.days.Day15;
+import java.awt.*;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Creates nodes and neighbours from a 2d grid. Each point in the map has an
+ * integer value that specifies the cost of crossing that point. If this value
+ * is negative, the point is unreachable.
+ *
+ * If diagonal movement is allowed, the Chebyshev distance is used, else
+ * Manhattan distance is used.
+ *
+ * @author Ben Ruijl
+ */
+public class Grid2d {
+ private final int[][] map;
+ private final boolean allowDiagonal;
+
+ public Grid2d(int[][] map, boolean allowDiagonal) {
+ this.map = map;
+ this.allowDiagonal = allowDiagonal;
+ }
+
+ public List findPath(Point start, Point end) {
+ return PathFinding.doAStar(new MapNode(start.x, start.y), new MapNode(end.x, end.y)).stream().map(MapNode::toPoint).collect(Collectors.toList());
+ }
+
+ /**
+ * A node in a 2d map. This is simply the coordinates of the point.
+ *
+ * @author Ben Ruijl
+ */
+ public class MapNode implements Node {
+ private final int x, y;
+
+ public MapNode(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public double getHeuristic(MapNode goal) {
+ if (allowDiagonal) {
+ return Math.max(Math.abs(x - goal.x), Math.abs(y - goal.y));
+ } else {
+ return Math.abs(x - goal.x) + Math.abs(y - goal.y);
+ }
+ }
+
+ public double getTraversalCost(MapNode neighbour) {
+ return 1 + map[neighbour.y][neighbour.x];
+ }
+
+ public Set getNeighbours() {
+ Set neighbours = new HashSet();
+
+ for (int i = x - 1; i <= x + 1; i++) {
+ for (int j = y - 1; j <= y + 1; j++) {
+ if ((i == x && j == y) || i < 0 || j < 0 || j >= map.length
+ || i >= map[j].length) {
+ continue;
+ }
+
+ if (!allowDiagonal &&
+ ((i < x && j < y) ||
+ (i < x && j > y) ||
+ (i > x && j > y) ||
+ (i > x && j < y))) {
+ continue;
+ }
+
+ if (map[j][i] == Day15.WALL || map[j][i] == Day15.UNEXPLORED) {
+ continue;
+ }
+
+ // TODO: create cache instead of recreation
+ neighbours.add(new MapNode(i, j));
+ }
+ }
+
+ return neighbours;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + x + ", " + y + ")";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getOuterType().hashCode();
+ result = prime * result + x;
+ result = prime * result + y;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MapNode other = (MapNode) obj;
+ if (!getOuterType().equals(other.getOuterType()))
+ return false;
+ if (x != other.x)
+ return false;
+ return y == other.y;
+ }
+
+ public Point toPoint() {
+ return new Point(x, y);
+ }
+
+ private Grid2d getOuterType() {
+ return Grid2d.this;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Node.java b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Node.java
new file mode 100644
index 00000000..8bbccfeb
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Node.java
@@ -0,0 +1,38 @@
+package com.sbaars.adventofcode.year19.pathfinding;
+
+import java.util.Set;
+
+/**
+ * A node in a graph, useful for pathfinding.
+ *
+ * @param Actual type of the node
+ * @author Ben Ruijl
+ */
+public interface Node {
+ /**
+ * The heuristic cost to move from the current node to the goal. In most
+ * cases this is the Manhattan distance or Chebyshev distance.
+ *
+ * @param goal
+ * @return
+ */
+ double getHeuristic(T goal);
+
+ /**
+ * The cost of moving from the current node to the neighbour. In most cases
+ * this is just the distance between the nodes, but additional terrain cost
+ * can be added as well (for example climbing a mountain is more expensive
+ * than walking on a road).
+ *
+ * @param neighbour Neighbour of current node
+ * @return Traversal cost
+ */
+ double getTraversalCost(T neighbour);
+
+ /**
+ * Gets the set of neighbouring nodes.
+ *
+ * @return Neighbouring nodes
+ */
+ Set getNeighbours();
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/pathfinding/PathFinding.java b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/PathFinding.java
new file mode 100644
index 00000000..792b9e6a
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/PathFinding.java
@@ -0,0 +1,85 @@
+package com.sbaars.adventofcode.year19.pathfinding;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+/**
+ * Helper class containing pathfinding algorithms.
+ *
+ * @author Ben Ruijl
+ */
+public class PathFinding {
+
+ /**
+ * A Star pathfinding. Note that the heuristic has to be monotonic:
+ * {@code h(x) <=
+ * d(x, y) + h(y)}.
+ *
+ * @param start Starting node
+ * @param goal Goal node
+ * @return Shortest path from start to goal, or null if none found
+ */
+ public static > List doAStar(T start, T goal) {
+ Set closed = new HashSet();
+ Map fromMap = new HashMap();
+ List route = new LinkedList();
+ Map gScore = new HashMap();
+ final Map fScore = new HashMap();
+ PriorityQueue open = new PriorityQueue(11, new Comparator() {
+
+ public int compare(T nodeA, T nodeB) {
+ return Double.compare(fScore.get(nodeA), fScore.get(nodeB));
+ }
+ });
+
+ gScore.put(start, 0.0);
+ fScore.put(start, start.getHeuristic(goal));
+ open.offer(start);
+
+ while (!open.isEmpty()) {
+ T current = open.poll();
+ if (current.equals(goal)) {
+ while (current != null) {
+ route.add(0, current);
+ current = fromMap.get(current);
+ }
+
+ return route;
+ }
+
+ closed.add(current);
+
+ for (T neighbour : current.getNeighbours()) {
+ if (closed.contains(neighbour)) {
+ continue;
+ }
+
+ double tentG = gScore.get(current)
+ + current.getTraversalCost(neighbour);
+
+ boolean contains = open.contains(neighbour);
+ if (!contains || tentG < gScore.get(neighbour)) {
+ gScore.put(neighbour, tentG);
+ fScore.put(neighbour, tentG + neighbour.getHeuristic(goal));
+
+ if (contains) {
+ open.remove(neighbour);
+ }
+
+ open.offer(neighbour);
+ fromMap.put(neighbour, current);
+ }
+ }
+ }
+
+ return new ArrayList<>();
+ }
+
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/util/CountMap.java b/src/main/java/com/sbaars/adventofcode/year19/util/CountMap.java
new file mode 100644
index 00000000..1498360d
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/util/CountMap.java
@@ -0,0 +1,58 @@
+package com.sbaars.adventofcode.year19.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class CountMap extends HashMap {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public CountMap() {
+ }
+
+ public CountMap(int initialCapacity, float loadFactor) {
+ super(initialCapacity, loadFactor);
+ }
+
+ public CountMap(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public CountMap(Map extends K, Integer> m) {
+ super(m);
+ }
+
+ public Integer increment(K key) {
+ return super.put(key, super.containsKey(key) ? super.get(key) + 1 : 1);
+ }
+
+ public Integer increment(K key, int amount) {
+ return super.put(key, super.containsKey(key) ? super.get(key) + amount : amount);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Integer get(Object key) {
+ if (!super.containsKey(key))
+ super.put((K) key, 0);
+ return super.get(key);
+ }
+
+ @Override
+ public String toString() {
+ return keySet().stream().sorted().map(e -> e + "\t" + get(e)).collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ public void addAll(CountMap amountPerCloneClassSize) {
+ amountPerCloneClassSize.entrySet().stream().forEach(e -> this.increment(e.getKey(), e.getValue()));
+ }
+
+ public void incrementAll(CountMap input) {
+ for (Entry i : input.entrySet()) {
+ increment(i.getKey(), i.getValue());
+ }
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/util/LongCountMap.java b/src/main/java/com/sbaars/adventofcode/year19/util/LongCountMap.java
new file mode 100644
index 00000000..32dca808
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/util/LongCountMap.java
@@ -0,0 +1,69 @@
+package com.sbaars.adventofcode.year19.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+
+public class LongCountMap extends HashMap {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public LongCountMap() {
+ }
+
+ public LongCountMap(int initialCapacity, float loadFactor) {
+ super(initialCapacity, loadFactor);
+ }
+
+ public LongCountMap(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public LongCountMap(Map extends K, Long> m) {
+ super(m);
+ }
+
+ public Long increment(K key) {
+ return super.put(key, super.containsKey(key) ? super.get(key) + 1 : 1);
+ }
+
+ public Long increment(K key, long amount) {
+ return super.put(key, super.containsKey(key) ? super.get(key) + amount : amount);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Long get(Object key) {
+ if (!super.containsKey(key))
+ super.put((K) key, 0L);
+ return super.get(key);
+ }
+
+ @Override
+ public String toString() {
+ return keySet().stream().sorted().map(e -> e + "\t" + get(e)).collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ public void addAll(LongCountMap amountPerCloneClassSize) {
+ amountPerCloneClassSize.entrySet().stream().forEach(e -> this.increment(e.getKey(), e.getValue()));
+ }
+
+ public void incrementAll(LongCountMap input) {
+ for (Entry