diff --git a/2022/README.md b/2022/README.md index d6d1b75bffa14c401092015a271e84fb2dd5567a..7520a7a3f97864ebf46419b6c2fa5f43ac9d399b 100644 --- a/2022/README.md +++ b/2022/README.md @@ -543,7 +543,28 @@ The subproblems are Since I created a `compareTo()` method as part of Part 1, Part 2 is easy. -## Day 14 +## Day 1 + +- [The problem](https://adventofcode.com/2022/day/14) +- [The solution](src/main/java/edu/unl/cse/bohn/year2022/Day14.java) + +### Part 1 + +The subproblems are +- Determine which locations are blocked +- Determine where a unit of sand moves to + - Can it move? + - Down, diagonally left, diagonally right? +- Determine the final status of a unit of sand + - Stationary, blocking a location + - Falling forever + +### Part 2 + +Same subproblems, except that falling forever isn't an option. +Instead, we'll have to pretend there's an infinitely-long floor. + +## Day 15 (coming soon) diff --git a/2022/src/main/java/edu/unl/cse/bohn/year2022/Day14.java b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day14.java new file mode 100644 index 0000000000000000000000000000000000000000..d630b76f015076d019f8f67642c08a7d0ac2bb2b --- /dev/null +++ b/2022/src/main/java/edu/unl/cse/bohn/year2022/Day14.java @@ -0,0 +1,178 @@ +package edu.unl.cse.bohn.year2022; + +import edu.unl.cse.bohn.Puzzle; + +import java.util.*; + +@SuppressWarnings("unused") +public class Day14 extends Puzzle { + + public Day14(boolean isProductionReady) { + super(isProductionReady); + sampleData = """ + 498,4 -> 498,6 -> 496,6 + 503,4 -> 502,4 -> 502,9 -> 494,9"""; + } + + public static final int SAND_ORIGIN = 500; + public static final boolean verbose = false; + + @Override + public long computePart1(List<String> data) { + for (String datum : data) { + Location.block(datum); + } + int amountOfRestingSand = 0; + Sand sand; + do { + sand = new Sand(); + //noinspection StatementWithEmptyBody + while (sand.fall() && !sand.isFallingForever()) { + } + if (sand.isStationary()) amountOfRestingSand++; + } while (!sand.isFallingForever()); + return amountOfRestingSand; + } + + @Override + public long computePart2(List<String> data) { + Location.reset(); + for (String datum : data) { + Location.block(datum); + } + Location.addFloor(); + int amountOfRestingSand = 0; + Sand sand; + do { + sand = new Sand(); + //noinspection StatementWithEmptyBody + while (sand.fall()) { + } + amountOfRestingSand++; + } while (!Location.isBlocked(SAND_ORIGIN, 0)); + return amountOfRestingSand; + } + + private record Location(int x, int y) { + private static Set<Location> blockedLocations = new HashSet<>(); + private static int maximumDepth = Integer.MIN_VALUE; + private static boolean hasFloor = false; + + public static void block(int x, int y) { + blockedLocations.add(new Location(x, y)); + if (verbose) { + System.out.println("Blocking (" + x + "," + y + ")" + + ((y > maximumDepth) ? " ** new maximum depth **" : "")); + } + if (!hasFloor && y > maximumDepth) { + maximumDepth = y; + } + } + + public static boolean isBlocked(int x, int y) { + return (hasFloor && y >= maximumDepth + 2) || blockedLocations.contains(new Location(x, y)); + } + + public static void block(String structure) { + if (structure.contains(" -> ")) { + int firstArrowIndex = structure.indexOf(" -> "); + int[] firstPoint = Arrays.stream(structure.substring(0, firstArrowIndex).strip().split(",")) + .mapToInt(Integer::parseInt).toArray(); + String restOftheStructure = structure.substring(firstArrowIndex + " -> ".length()); + int endOfNextPointIndex = restOftheStructure.indexOf(" -> "); + endOfNextPointIndex = endOfNextPointIndex == -1 ? restOftheStructure.length() : endOfNextPointIndex; + int[] nextPoint = Arrays.stream(restOftheStructure.substring(0, endOfNextPointIndex).strip().split(",")) + .mapToInt(Integer::parseInt).toArray(); + int x = firstPoint[0]; + int y = firstPoint[1]; + while (x != nextPoint[0] || y != nextPoint[1]) { + Location.block(x, y); + if (x < nextPoint[0]) x++; + if (x > nextPoint[0]) x--; + if (y < nextPoint[1]) y++; + if (y > nextPoint[1]) y--; + } + block(restOftheStructure); + } else { + int[] coordinates = Arrays.stream(structure.strip().split(",")).mapToInt(Integer::parseInt).toArray(); + block(coordinates[0], coordinates[1]); + } + } + + public static int getDeepestDepths() { + return maximumDepth; + } + + public static void reset() { + blockedLocations = new HashSet<>(); + maximumDepth = Integer.MIN_VALUE; + hasFloor = false; + } + + public static void addFloor() { + hasFloor = true; + } + + public static void removeFloor() { + hasFloor = false; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (!(other instanceof Location location)) return false; + return x == location.x && y == location.y; + } + } + + private static class Sand { + private int x, y; + + public Sand() { + x = SAND_ORIGIN; + y = 0; + } + + /** + * Causes a grain of sand to move if it can. If (x,y+1) is unblocked, then the sand moves to (x,y+1). + * Otherwise, if (x-1,y+1) is unblocked, then the sand moves to (x-1,y+1). + * Otherwise, if (x+1,y+1) is unblocked, then the sand moves to (x+1,y+1). + * Otherwise, the sand remains stationary. + * + * @return <code>true</code> if the sand moves, <code>false</code> if the sand remains stationary + */ + public boolean fall() { + boolean moved = false; + if (!Location.isBlocked(x, y + 1)) { + y++; + moved = true; + } else if (!Location.isBlocked(x - 1, y + 1)) { + x--; + y++; + moved = true; + } else if (!Location.isBlocked(x + 1, y + 1)) { + x++; + y++; + moved = true; + } else { + Location.block(x, y); + if (verbose) { + System.out.println("Sand is resting at " + x + "," + y); + } + } + return moved; + } + + public boolean isStationary() { + return Location.isBlocked(x, y + 1) && Location.isBlocked(x - 1, y + 1) && Location.isBlocked(x + 1, y + 1); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean isFallingForever() { + if (verbose && y > Location.getDeepestDepths()) { + System.out.println("Sand at " + x + "," + y + " is falling forever"); + } + return y > Location.getDeepestDepths(); + } + } +}