From 1b145556a7b10065e5e9f9e9ce0f26380ede4b37 Mon Sep 17 00:00:00 2001 From: Xevion Date: Fri, 30 Apr 2021 18:48:16 -0500 Subject: [PATCH] A+ January 2017: Doubled --- uil/aplus-january-2017/src/Doubled.java | 403 ++++++++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 uil/aplus-january-2017/src/Doubled.java diff --git a/uil/aplus-january-2017/src/Doubled.java b/uil/aplus-january-2017/src/Doubled.java new file mode 100644 index 0000000..06e0314 --- /dev/null +++ b/uil/aplus-january-2017/src/Doubled.java @@ -0,0 +1,403 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.util.*; +import java.util.stream.Collectors; + +class StackerPosition +{ + int x; // Column + int y; // Row + Character type; // Nullable character + Stacker grid; // Reference to a character grid for synchronizing type + + StackerPosition(int x, int y) + { + this(null, x, y, null); + } + + StackerPosition(int x, int y, Character type) + { + this(null, x, y, type); + } + + StackerPosition(Stacker grid, int x, int y) + { + this(grid, x, y, grid.safeGetType(x, y)); + } + + StackerPosition(Stacker grid, int x, int y, Character type) + { + this.grid = grid; + this.x = x; + this.y = y; + this.type = type; + } + + public static int countUniques(List initial) + { + Map counts = new HashMap<>(); + + // Increment by one + for (Connectable connect : initial) { + int key = connect.hashCode(); + counts.put( + key, counts.getOrDefault(key, 0) + 1 + ); + } + + return counts.size(); + } + + public StackerPosition inverse() + { + return new StackerPosition(grid, -x, -y); + } + + public StackerPosition add(StackerPosition other) + { + return new StackerPosition(grid, this.x + other.x, this.y + other.y); + } + + public StackerPosition subtract(StackerPosition other) + { + return new StackerPosition(grid, this.x - other.x, this.y - other.y); + } + + public StackerPosition normalize() + { +// int mag = (int) Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); +// return new StackerPosition(x / mag, y / mag); + return new StackerPosition(x != 0 ? x / Math.abs(x) : 0, y != 0 ? y / Math.abs(y) : 0); + } + + @Override + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StackerPosition that = (StackerPosition) o; + return x == that.x && + y == that.y; + } + + @Override + public int hashCode() + { + return Objects.hash(x, y); + } +} + +enum Direction +{ + NORTH, + NORTH_EAST, + EAST, + SOUTH_EAST, + SOUTH, + SOUTH_WEST, + WEST, + NORTH_WEST; + + /** + * @param from The position the node starts from + * @param to The position the node ends at + * @return A Direction Enum describing the cardinal/ordinal direction a StackerPosition is from another. + */ + public static Direction getDirection(StackerPosition from, StackerPosition to) + { + StackerPosition offset = from.subtract(to).normalize(); + if (offset.x == 0) { + if (offset.y == 1) return Direction.NORTH; + else if (offset.y == -1) return Direction.SOUTH; + } else if (offset.x == 1) { + if (offset.y == 0) return Direction.WEST; + else if (offset.y == 1) return Direction.NORTH_WEST; + else if (offset.y == -1) return Direction.SOUTH_WEST; + } else if (offset.x == -1) { + if (offset.y == 0) return Direction.EAST; + else if (offset.y == 1) return Direction.NORTH_EAST; + else if (offset.y == -1) return Direction.SOUTH_EAST; + } + return null; + } +} + +class Connectable +{ + StackerPosition[] filled; // Positions in the stacker that have a checker inside them + StackerPosition last; // The last position in a four-checker line needed to be filled + char commonType; // The common check type among the filled checkers + Direction direction; // The direction from the unfilled area to the other filled checkers. + + Connectable(StackerPosition[] filled, StackerPosition last) + { + this.filled = filled; + this.last = last; + this.commonType = filled[0].type; + this.direction = Direction.getDirection(this.last, filled[0]); + } + + @Override + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Connectable that = (Connectable) o; + return last.equals(that.last) && + direction == that.direction; + } + + @Override + public int hashCode() + { + return Objects.hash(last); + } + + @Override + public String toString() + { + return String.format("Connectable{%s, (%d, %d) -> %s}", commonType, last.x, last.y, direction); + } +} + +class Stacker +{ + private char[][] grid = new char[6][]; + + public static final char NONE = '.'; + public static final char BLACK = 'B'; + public static final char RED = 'R'; + + public static final StackerPosition[] directions = new StackerPosition[]{ + new StackerPosition(1, 0), + new StackerPosition(0, 1), + new StackerPosition(1, 1), + new StackerPosition(1, -1) + }; + + public List getOpenSlots() + { + List open = new ArrayList<>(); + + // Search from left to right + for (int c = 0; c < 7; c++) { + // Search from bottom to top + for (int r = 5; r >= 0; r--) { + // If open, it's open to air all the way up + if (grid[r][c] == NONE) { + open.add(new StackerPosition(this, c, r)); + break; + } + } + } + + return open; + } + + /** + * @return True if the position in the grid can have a checker placed in it directly, considering gravity. + */ + public boolean isPlaceable(StackerPosition pos) + { + return isPlaceable(pos.x, pos.y); + } + + /** + * @return True if the position in the grid can have a checker placed in it directly, considering gravity. + */ + public boolean isPlaceable(int x, int y) + { + if (!isValid(x, y)) return false; // Position must be valid + if (getType(x, y) != NONE) return false; // Position must not have a checker in it + if (y == 5) return true; // If it's on the bottom row, it's placeable + else return getType(x, y + 1) != NONE; // If it has something below it, it's placeable + } + + public List findConnectables(char type) + { + List found = new ArrayList<>(); + + // Iterate along each checker position in the grid + for (int r = 0; r < 6; r++) { + for (int c = 0; c < 7; c++) { + // If the checker is of the type we want + if (grid[r][c] == type) { + StackerPosition position = new StackerPosition(this, c, r); + + // Check each direction if it has three checkers in a row + for (StackerPosition direction : directions) { + List checkQueue = new ArrayList<>(); + checkQueue.add(position.add(direction)); + checkQueue.add(position.subtract(direction)); + + // Check if the base triple has been formed + if (validateAll(checkQueue, type)) { + // Check if right side node is free + StackerPosition emptyFront = position.add(direction).add(direction); + StackerPosition emptyBack = position.subtract(direction).subtract(direction); + + // Check if the right/up side node is free + if (isValid(emptyFront) && getType(emptyFront) == NONE) + found.add(new Connectable(checkQueue.toArray(new StackerPosition[3]), emptyFront)); + + // Check if left/down side node is free + if (isValid(emptyBack) && getType(emptyBack) == NONE) + found.add(new Connectable(checkQueue.toArray(new StackerPosition[3]), emptyBack)); + } + } + } + } + } + + // Filter out connectables where the last node cannot be placed properly + found = found.stream().filter(connectable -> isPlaceable(connectable.last)).collect(Collectors.toList()); + + return found; + } + + /** + * @param positions A list of StackerPosition objects + * @return True if all positions are valid and of the same type. + */ + private boolean validateAll(List positions) + { + StackerPosition first = positions.get(0); + if (isValid(first)) + return validateAll(positions, getType(first)); + return false; + } + + /** + * @param positions A list of StackerPosition objects + * @param type The type of checker that all StackerPosition objects must be + * @return True if all positions are valid and of the same type as requested. + */ + private boolean validateAll(List positions, char type) + { + for (StackerPosition next : positions) { + if (!isValid(next) || getType(next) != type) + return false; + } + return true; + } + + /** + * @return Returns true if the given position is valid within the grid. + */ + private boolean isValid(StackerPosition position) + { + return position.x >= 0 && position.x < 7 && position.y >= 0 && position.y < 6; + } + + /** + * @return Returns true if the given position is valid within the grid. + */ + public boolean isValid(int x, int y) + { + return x >= 0 && x < 7 && y >= 0 && y < 6; + } + + /** + * @return Returns the checker type (char) at the given position in the grid + */ + private char getType(StackerPosition position) + { + return grid[position.y][position.x]; + } + + /** + * @return Returns the check type (char) at the given position in the grid + */ + public Character getType(int x, int y) + { + return grid[y][x]; + } + + /** + * Simply checks if the position in the grid is valid, if so, returns the type character at that position. + * + * @return Returns the check type (char) at the given position in the grid, if valid. Else null. + */ + public Character safeGetType(int x, int y) + { + return isValid(x, y) ? getType(x, y) : null; + } + + public void setRow(int r, char[] row) + { + grid[r] = row; + } + + @Override + public Stacker clone() + { + Stacker clone = new Stacker(); + clone.grid = new char[6][]; + for (int i = 0; i < 6; i++) + clone.grid[i] = Arrays.copyOf(grid[i], grid[i].length); + return clone; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + for (int r = 0; r < 6; r++) { + sb.append(String.valueOf(grid[r])); + sb.append("\n"); + } + + // Remove the extra newline at the end + sb.setLength(sb.length() - 1); + + return sb.toString(); + } + + public void placeChecker(int x, int y, char type) + { + grid[y][x] = type; + } +} + +public class Doubled +{ + public static void main(String[] args) throws FileNotFoundException + { + Scanner scanner = new Scanner(new File("doubled.dat")); + int inputs = scanner.nextInt(); + scanner.nextLine(); + + for (int i = 0; i < inputs; i++) { + Stacker stacker = new Stacker(); + char player = scanner.nextLine().charAt(0); + char opposite = player == 'R' ? 'B' : 'R'; + + for (int r = 0; r < 6; r++) { + char[] row = scanner.nextLine().toCharArray(); + stacker.setRow(r, row); + } + + List initial = stacker.findConnectables(opposite); + boolean doubled = false; + + if (StackerPosition.countUniques(initial) > 1) { + doubled = true; + } else if (initial.size() == 1) { + Connectable attempt = initial.get(0); + + // Create a temporary copy of the Connect 4 Stacker + Stacker temp = stacker.clone(); + + // Place a checker in the same place + temp.placeChecker(attempt.last.x, attempt.last.y, player); + + // See if any new connectables opened up. + List post = temp.findConnectables(opposite); + if (post.size() > 0) + doubled = true; + } + + System.out.println(doubled ? "Doubled!" : "There's hope"); + } + } +}