package dev.forstenlechner.aoc; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; public class Day11 { private enum SeatState { EMPTY { @Override void print() { System.out.print("L"); } }, OCCUPIED { @Override void print() { System.out.print("#"); } }, FLOOR { @Override void print() { System.out.print("."); } }; abstract void print(); static SeatState from(char c) { return switch (c) { case 'L' -> EMPTY; case '#' -> OCCUPIED; default -> FLOOR; }; } } private record Location(int row, int col) {}; private enum Directon { UP { @Override Location next(Location cur) { return new Location(cur.row() -1, cur.col); } }, DOWN { @Override Location next(Location cur) { return new Location(cur.row() +1, cur.col); } }, LEFT{ @Override Location next(Location cur) { return new Location(cur.row(), cur.col -1); } }, RIGHT{ @Override Location next(Location cur) { return new Location(cur.row(), cur.col +1); } }, LEFT_UP { @Override Location next(Location cur) { return new Location(cur.row() -1, cur.col -1); } }, LEFT_DOWN { @Override Location next(Location cur) { return new Location(cur.row() +1, cur.col -1); } }, RIGHT_UP { @Override Location next(Location cur) { return new Location(cur.row() -1, cur.col +1); } }, RIGHT_DOWN { @Override Location next(Location cur) { return new Location(cur.row() +1, cur.col +1); } }; abstract Location next(Location cur); } public static void main(String[] args) throws Exception { new Day11().solve(); } private List> copySeatMap(List> seatMap) { return seatMap.stream().map(ArrayList::new).collect(toList()); } private void solve() throws Exception { Stream lines = Files.lines(Paths.get("day11/input")); List> seatMap = lines .map(l -> l.chars().mapToObj(c -> (char) c).map(SeatState::from).collect(toList())) .collect(toList()); solveFirst(seatMap); solveSecond(seatMap); } private void printMap(List> seatMap) { seatMap.forEach(row -> { row.forEach(SeatState::print); System.out.println(); }); } private void solveSecond(List> seatMap) { boolean changed = true; while (changed) { // printMap(seatMap); // System.out.println(); changed = false; var nextSeatMap = copySeatMap(seatMap); for (int row = 0; row < seatMap.size(); row++) { for (int column = 0; column < seatMap.get(row).size(); column++) { SeatState prev = seatMap.get(row).get(column); SeatState next = calcSeatWithDirection(seatMap, row, column); changed |= prev != next; nextSeatMap.get(row).set(column, next); } } seatMap = nextSeatMap; } Long occupiedSeats = seatMap.stream().map(col -> col.stream().filter(state -> state == SeatState.OCCUPIED).count()).reduce(0L, Long::sum); System.out.println(occupiedSeats); } private void solveFirst(List> seatMap) { boolean changed = true; while (changed) { changed = false; var nextSeatMap = copySeatMap(seatMap); for (int row = 0; row < seatMap.size(); row++) { for (int column = 0; column < seatMap.get(row).size(); column++) { SeatState prev = seatMap.get(row).get(column); SeatState next = calcSeat(seatMap, row, column); changed |= prev != next; nextSeatMap.get(row).set(column, next); } } seatMap = nextSeatMap; } Long occupiedSeats = seatMap.stream().map(col -> col.stream().filter(state -> state == SeatState.OCCUPIED).count()).reduce(0L, Long::sum); System.out.println(occupiedSeats); } private SeatState calcSeat(List> seatMap, int row, int column) { int occupiedSeatsAround = 0; occupiedSeatsAround += isSeatOccupiedInt(seatMap, row-1, column-1); occupiedSeatsAround += isSeatOccupiedInt(seatMap, row-1, column); occupiedSeatsAround += isSeatOccupiedInt(seatMap, row-1, column+1); occupiedSeatsAround += isSeatOccupiedInt(seatMap, row+1, column-1); occupiedSeatsAround += isSeatOccupiedInt(seatMap, row+1, column); occupiedSeatsAround += isSeatOccupiedInt(seatMap, row+1, column+1); occupiedSeatsAround += isSeatOccupiedInt(seatMap, row, column-1); occupiedSeatsAround += isSeatOccupiedInt(seatMap, row, column+1); if (seatMap.get(row).get(column) == SeatState.EMPTY && occupiedSeatsAround == 0) { return SeatState.OCCUPIED; } if (seatMap.get(row).get(column) == SeatState.OCCUPIED && occupiedSeatsAround >= 4) { return SeatState.EMPTY; } return seatMap.get(row).get(column); } private int isSeatOccupiedInt(List> seatMap, int row, int column) { if (row >= 0 && row < seatMap.size() && column >= 0 && column < seatMap.get(row).size()) { return seatMap.get(row).get(column) == SeatState.OCCUPIED ? 1 : 0; } return 0; } private SeatState calcSeatWithDirection(List> seatMap, int row, int column) { int occupiedSeatsAround = 0; occupiedSeatsAround += isSeatOccupiedIntWithDirection(seatMap, row, column, Directon.UP); occupiedSeatsAround += isSeatOccupiedIntWithDirection(seatMap, row, column, Directon.DOWN); occupiedSeatsAround += isSeatOccupiedIntWithDirection(seatMap, row, column, Directon.LEFT); occupiedSeatsAround += isSeatOccupiedIntWithDirection(seatMap, row, column, Directon.RIGHT); occupiedSeatsAround += isSeatOccupiedIntWithDirection(seatMap, row, column, Directon.LEFT_UP); occupiedSeatsAround += isSeatOccupiedIntWithDirection(seatMap, row, column, Directon.LEFT_DOWN); occupiedSeatsAround += isSeatOccupiedIntWithDirection(seatMap, row, column, Directon.RIGHT_UP); occupiedSeatsAround += isSeatOccupiedIntWithDirection(seatMap, row, column, Directon.RIGHT_DOWN); if (seatMap.get(row).get(column) == SeatState.EMPTY && occupiedSeatsAround == 0) { return SeatState.OCCUPIED; } if (seatMap.get(row).get(column) == SeatState.OCCUPIED && occupiedSeatsAround >= 5) { // change in rule! return SeatState.EMPTY; } return seatMap.get(row).get(column); } private int isSeatOccupiedIntWithDirection(List> seatMap, int row, int column, Directon directon) { return isSeatOccupiedIntWithDirection(seatMap, new Location(row, column), directon); } private int isSeatOccupiedIntWithDirection(List> seatMap, Location test, Directon directon) { test = directon.next(test); while (test.row() >= 0 && test.row() < seatMap.size() && test.col() >= 0 && test.col() < seatMap.get(test.row()).size() && seatMap.get(test.row).get(test.col()) == SeatState.FLOOR) { test = directon.next(test); } if (test.row() >= 0 && test.row() < seatMap.size() && test.col() >= 0 && test.col() < seatMap.get(test.row()).size()) { return seatMap.get(test.row()).get(test.col()) == SeatState.OCCUPIED ? 1 : 0; } return 0; } }