import Foundation enum Move: Character, CustomStringConvertible { case n = "^" case s = "v" case e = ">" case w = "<" var description: String { return String(self.rawValue) } var delta: (Int, Int) { switch self { case .n: return (0, -1) case .s: return (0, 1) case .e: return (1, 0) case .w: return (-1, 0) } } } struct Item: Hashable { let (x, y): (Int, Int) var rightCell: Item { return Item(x: x+1, y: y) } var gps: Int { return x + 100*y } func move(_ m: Move) -> Item { let (dx, dy) = m.delta return Item(x: x + dx, y: y + dy) } } struct Boxes { var boxes: Set = [] func at(x: Int, y: Int) -> Item? { if boxes.contains(Item(x: x, y: y)) { return Item(x: x, y: y) } if boxes.contains(Item(x: x-1, y: y)) { return Item(x: x-1, y: y) } return nil } } struct Map: CustomStringConvertible { let walls: Set var boxes: Boxes = Boxes() var bot = Item(x: 0, y: 0) var (w, h): (Int, Int) var description: String { var s: [[String]] = Array( repeating: Array(repeating: " ", count: w*2), count: h ) walls.forEach { w in s[w.y][w.x] = "🮘🮘" } boxes.boxes.forEach { b in s[b.y][b.x] = "🬴🬰"; s[b.y][b.x+1] = "🬰🬸" } s[bot.y][bot.x] = "\u{001B}[91m🮿 \u{001B}[0m" return (" " + (0..<2*w).map { " \($0 % 10)" }.joined() + "\n") + s.enumerated().map { i, row in String(format: "%3d ", i) + row.joined() }.joined(separator: "\n") } var gps: Int { return boxes.boxes.reduce(0) { s, box in s + box.gps } } init(from s: ArraySlice) { var walls: Set = [] h = s.count w = s[0].count for (y, line) in s.enumerated() { for (x0, char) in line.enumerated() { let x = 2*x0 let item = Item(x: x, y: y) switch char { case "#": walls.formUnion([item, item.rightCell]) case "O": boxes.boxes.insert(item) case "@": bot = item default: () } } } self.walls = walls } mutating func move(_ dir: Move) { var boxesToPush: Set = [] var dests: Set = [bot.move(dir)] while !dests.isEmpty { let next = dests.removeFirst() if let box = boxes.at(x: next.x, y: next.y) { boxesToPush.insert(box) let movedBox = box.move(dir) switch dir { case Move.w: dests.insert(movedBox) case Move.e: dests.insert(movedBox.rightCell) case Move.n, Move.s: dests.insert(movedBox) dests.insert(movedBox.rightCell) } continue } if walls.contains(next) { return } } boxesToPush.forEach { boxes.boxes.remove($0) } boxesToPush.forEach { boxes.boxes.insert($0.move(dir)) } bot = bot.move(dir) } } func readInput(_ filePath: String) throws -> (Map, [Move]) { let content = try String(contentsOfFile: filePath, encoding: .ascii) let lines = content.split(separator: "\n", omittingEmptySubsequences: false) let blankIdx = lines.firstIndex(of: "")! let moves = lines.suffix(from: blankIdx + 1).joined().map { Move(rawValue: $0)! } return (Map(from: lines.prefix(blankIdx)), moves) } var (map, moves) = try readInput(CommandLine.arguments[1]) print(moves) for move in moves { //print(map) map.move(move) //print(move) } print(map) print(map.gps)