From ba6cee935ad813644547453474832e5bcfa122ad Mon Sep 17 00:00:00 2001 From: Sven Weidauer Date: Thu, 23 Dec 2021 20:20:12 +0100 Subject: [PATCH] Day 23 --- AoC21.xcodeproj/project.pbxproj | 85 ++++++++++ day23.swift | 284 ++++++++++++++++++++++++++++++++ 2 files changed, 369 insertions(+) create mode 100644 day23.swift diff --git a/AoC21.xcodeproj/project.pbxproj b/AoC21.xcodeproj/project.pbxproj index 6151db3..d15ff6e 100644 --- a/AoC21.xcodeproj/project.pbxproj +++ b/AoC21.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 26132D372774C886004F0228 /* day23.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26132D2B2774C871004F0228 /* day23.swift */; }; + 26132D382774C886004F0228 /* common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269BE5CB2762A08800871C85 /* common.swift */; }; 2615545A276A6C2C00374D18 /* day14.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26155459276A6C2C00374D18 /* day14.swift */; }; 2615545B276A6C3200374D18 /* common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 269BE5CB2762A08800871C85 /* common.swift */; }; 26155468276A6D0A00374D18 /* day15.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26155467276A6D0A00374D18 /* day15.swift */; }; @@ -44,6 +46,15 @@ /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ + 26132D2E2774C87D004F0228 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 26155450276A6C1C00374D18 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -200,6 +211,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 26132D2B2774C871004F0228 /* day23.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = day23.swift; sourceTree = ""; }; + 26132D302774C87D004F0228 /* Day23 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Day23; sourceTree = BUILT_PRODUCTS_DIR; }; 26155452276A6C1C00374D18 /* Day14 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Day14; sourceTree = BUILT_PRODUCTS_DIR; }; 26155459276A6C2C00374D18 /* day14.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = day14.swift; sourceTree = ""; }; 26155460276A6CF700374D18 /* Day15 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Day15; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -238,6 +251,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 26132D2D2774C87D004F0228 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2615544F276A6C1C00374D18 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -363,6 +383,7 @@ 263BA594275E974800839C92 = { isa = PBXGroup; children = ( + 26132D2B2774C871004F0228 /* day23.swift */, 2680ECFB27732A9400CAB23C /* day22.swift */, 2680ECE22771D82400CAB23C /* day21.swift */, 2680ECDF2770A2FC00CAB23C /* day20.swift */, @@ -405,6 +426,7 @@ 2680ECD82770A2E900CAB23C /* Day20 */, 2680ECE72771D82F00CAB23C /* Day21 */, 2680ECF427732A8300CAB23C /* Day22 */, + 26132D302774C87D004F0228 /* Day23 */, ); name = Products; sourceTree = ""; @@ -412,6 +434,23 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 26132D2F2774C87D004F0228 /* Day23 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 26132D342774C87D004F0228 /* Build configuration list for PBXNativeTarget "Day23" */; + buildPhases = ( + 26132D2C2774C87D004F0228 /* Sources */, + 26132D2D2774C87D004F0228 /* Frameworks */, + 26132D2E2774C87D004F0228 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Day23; + productName = Day23; + productReference = 26132D302774C87D004F0228 /* Day23 */; + productType = "com.apple.product-type.tool"; + }; 26155451276A6C1C00374D18 /* Day14 */ = { isa = PBXNativeTarget; buildConfigurationList = 26155458276A6C1C00374D18 /* Build configuration list for PBXNativeTarget "Day14" */; @@ -712,6 +751,9 @@ LastUpgradeCheck = 1310; ORGANIZATIONNAME = 5sw; TargetAttributes = { + 26132D2F2774C87D004F0228 = { + CreatedOnToolsVersion = 13.2.1; + }; 26155451276A6C1C00374D18 = { CreatedOnToolsVersion = 13.1; LastSwiftMigration = 1310; @@ -806,11 +848,21 @@ 2680ECD72770A2E900CAB23C /* Day20 */, 2680ECE62771D82F00CAB23C /* Day21 */, 2680ECF327732A8300CAB23C /* Day22 */, + 26132D2F2774C87D004F0228 /* Day23 */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ + 26132D2C2774C87D004F0228 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 26132D372774C886004F0228 /* day23.swift in Sources */, + 26132D382774C886004F0228 /* common.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2615544E276A6C1C00374D18 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -967,6 +1019,30 @@ /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ + 26132D352774C87D004F0228 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 722B335UM5; + ENABLE_HARDENED_RUNTIME = YES; + MACOSX_DEPLOYMENT_TARGET = 12.1; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 26132D362774C87D004F0228 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 722B335UM5; + ENABLE_HARDENED_RUNTIME = YES; + MACOSX_DEPLOYMENT_TARGET = 12.1; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; 26155456276A6C1C00374D18 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1616,6 +1692,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 26132D342774C87D004F0228 /* Build configuration list for PBXNativeTarget "Day23" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 26132D352774C87D004F0228 /* Debug */, + 26132D362774C87D004F0228 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 26155458276A6C1C00374D18 /* Build configuration list for PBXNativeTarget "Day14" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/day23.swift b/day23.swift new file mode 100644 index 0000000..a8be573 --- /dev/null +++ b/day23.swift @@ -0,0 +1,284 @@ + +@main +struct Day23: Puzzle { + func run() { + part1() + part2() + } + + func part1() { + var board = Board(height: 3) + board.fillHomeRow(for: .a, pieces: [.c, .c]) + board.fillHomeRow(for: .b, pieces: [.b, .d]) + board.fillHomeRow(for: .c, pieces: [.a, .a]) + board.fillHomeRow(for: .d, pieces: [.d, .b]) + board.show() + print("Part 1:", find(start: board)) + } + + func part2() { + var board = Board(height: 5) + board.fillHomeRow(for: .a, pieces: [.c, .d, .d, .c]) + board.fillHomeRow(for: .b, pieces: [.b, .c, .b, .d]) + board.fillHomeRow(for: .c, pieces: [.a, .b, .a, .a]) + board.fillHomeRow(for: .d, pieces: [.d, .a, .c, .b]) + board.show() + print("Part 2:", find(start: board)) + } + + func find(start: Board) -> Int { + let target = Board.makeGoal(height: start.height) + var current = start + var totalCost = 0 + + var possible: [Board: Int] = [:] + var visited: Set = [] + + + while current != target { + visited.insert(current) + + for (next, cost) in current.possibleMoves() where !visited.contains(next) { + possible[next] = min(possible[next, default: .max], totalCost + cost) + } + + (current, totalCost) = possible.min { $0.value < $1.value }! + + possible.removeValue(forKey: current) + } + + return totalCost + } + +} + +struct Board: Hashable { + enum Piece: CaseIterable, Hashable, CustomStringConvertible { + case a, b, c, d + + var homeColumn: Int { + switch self { + case .a: + return 2 + case .b: + return 4 + case .c: + return 6 + case .d: + return 8 + } + } + + var cost: Int { + switch self { + case .a: + return 1 + case .b: + return 10 + case .c: + return 100 + case .d: + return 1000 + } + } + + var sign: Character { + switch self { + case .a: + return "A" + case .b: + return "B" + case .c: + return "C" + case .d: + return "D" + } + } + + var description: String { String(sign) } + } + enum Cell: Hashable { + case piece(Piece) + case empty + case outside + } + + var width = 11 + var height = 3 + + var board: [Cell] + + init(height: Int) { + self.height = height + board = Array(repeating: .outside, count: width * height) + for x in 0.. Self { + var board = Self(height: height) + for piece in Piece.allCases { + for y in 1.. [(Piece, Int, Int)] { + var result: [(Piece, Int, Int)] = [] + for x in 0.. [(Board, Int)] { + let pieces = movablePieces() + .filter { !isHome(piece: $0.0, x: $0.1, y: $0.2) } + + var result: [(Board, Int)] = [] + for (piece, x, y) in pieces { + if let newY = freeHomeRowPosition(piece: piece), freeCorridor(from: x, to: piece.homeColumn) { + result.append(move(piece: piece, from: (x, y), to: (piece.homeColumn, newY))) + } + + for newX in openCorridorPositions(x: x) { + result.append(move(piece: piece, from: (x, y), to: (newX, 0))) + } + } + + return result + } + + func freeCorridor(from x0: Int, to x1: Int) -> Bool { + let minX: Int + let maxX: Int + + if x0 < x1 { (minX, maxX) = (x0 + 1, x1) } + else { (minX, maxX) = (x1, x0 - 1) } + + for x in minX...maxX { + if self[x, 0] != .empty { + return false + } + } + + return true + } + + func move(piece: Piece, from: (Int, Int), to: (Int, Int)) -> (Board, Int) { + let (x, y) = from + let (newX, newY) = to + + precondition(self[newX,newY] == .empty) + + let cost = (y + distance((x, 0), (newX, newY))) * piece.cost + var board = self + board[x, y] = .empty + board[newX, newY] = .piece(piece) + + return (board, cost) + } + + func freeHomeRowPosition(piece: Piece) -> Int? { + let x = piece.homeColumn + var depth = height - 1 + + loop: while depth > 0 { + switch self[x, depth] { + case .piece(piece): break + case .piece: return nil + case .empty: break loop + case .outside: preconditionFailure("Invalid board") + } + depth -= 1 + } + + return depth + } + + static let homeColumns = Set(Piece.allCases.map(\.homeColumn)) + + func openCorridorPositions(x: Int) -> [Int] { + var xmin = x + var xmax = x + + while xmin > 0 && self[xmin, 0] == .empty { + xmin -= 1 + } + + while xmax < width && self[xmax, 0] == .empty { + xmax += 1 + } + + let result = (xmin.. Int { + let (x0, y0) = a + let (x1, y1) = b + + return abs(x1 - x0) + abs(y1 - y0) + } + + func isHome(piece: Piece, x: Int, y: Int) -> Bool { + guard x == piece.homeColumn && y > 0 else { return false } + for otherY in (y + 1).. Cell { + get { + board[x + width * y] + } + set { + board[x + width * y] = newValue + } + } +}