From 5531412a55cbc95a77ee50ca23f72dece6e7feb4 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 8 Jan 2019 23:15:45 -0600 Subject: [PATCH] Add code for Day 15 Day 15: Beverage Bandits --- Sources/AOC2018/data/day15.txt | 32 ++ Sources/AOC2018/day15.swift | 789 +++++++++++++++++++++++++++++++++ Sources/AOC2018/main.swift | 4 +- Sources/AOC2018/tools.swift | 27 +- 4 files changed, 846 insertions(+), 6 deletions(-) create mode 100644 Sources/AOC2018/data/day15.txt create mode 100644 Sources/AOC2018/day15.swift diff --git a/Sources/AOC2018/data/day15.txt b/Sources/AOC2018/data/day15.txt new file mode 100644 index 0000000..627beab --- /dev/null +++ b/Sources/AOC2018/data/day15.txt @@ -0,0 +1,32 @@ +################################ +#########################.G.#### +#########################....### +##################.G.........### +##################.##.......#### +#################...#.........## +################..............## +######..########...G...#.#....## +#####....######.G.GG..G..##.#### +#######.#####G............#.#### +#####.........G..G......#...#### +#####..G......G..........G....## +######GG......#####........E.### +#######......#######..........## +######...G.G#########........### +######......#########.....E..### +#####.......#########........### +#####....G..#########........### +######.##.#.#########......##### +#######......#######.......##### +#######.......#####....E...##### +##.G..#.##............##.....### +#.....#........###..#.#.....#### +#.........E.E...#####.#.#....### +######......#.....###...#.#.E### +#####........##...###..####..### +####...G#.##....E####E.####...## +####.#########....###E.####....# +###...#######.....###E.####....# +####..#######.##.##########...## +####..######################.### +################################ diff --git a/Sources/AOC2018/day15.swift b/Sources/AOC2018/day15.swift new file mode 100644 index 0000000..74b485f --- /dev/null +++ b/Sources/AOC2018/day15.swift @@ -0,0 +1,789 @@ +// +// Advent of Code 2018 "Day 15: Beverage Bandits" +// + +import Foundation + +enum State: Int { + case Unknown = -4 + case Wall = -3 + case Open = 0 + case Elf = 1 + case Goblin = 2 +} + +struct Entity: Equatable, Comparable { + var kind: State + var loc: GridPoint + var power: Int + var hitPts: Int + + init(kind: State, loc: GridPoint, power: Int = 3, hitPts: Int = 200) { + self.kind = kind + self.loc = loc + self.power = power + self.hitPts = hitPts + } + static func == (lhs: Entity, rhs: Entity) -> Bool { + return (lhs.loc == rhs.loc) && (lhs.power == rhs.power) && (lhs.hitPts == rhs.hitPts) + } + + static func < (lhs: Entity, rhs: Entity) -> Bool { + return lhs.loc < rhs.loc + } +} + +class Beverage { + var entities: [Entity] = [] + var maze: [[Character]] = [] + var nummaze: [[State]] = [] + var entityDict: [GridPoint : Int] = [:] + var width = 0 + var height = 0 + var rounds = 0 + + // Supply filename for input ex: '/home/peterr/AOC2018/Sources/AOC2018/data/day15.txt' + init(withFile filename: String) { + let mazeFile = Tools.readFile(fromPath: filename) + var mazeStrings = mazeFile.components(separatedBy: "\n") + // This gurd statement is just an excuse to use guard + // I'm assuming the last string is an empty string, if not DON'T remove the last string + guard let lastStr = mazeStrings.last, lastStr.count == 0 else { return } + mazeStrings.removeLast() // empty string + parseData(intoGrid: mazeStrings) + } + + // Supply test data in the form of a String + init(withString mazeFile: String) { + let mazeStrings = mazeFile.components(separatedBy: "\n") + parseData(intoGrid: mazeStrings) + } + + func parseData(intoGrid mazeArray: [String]) { + guard mazeArray.count > 0 else { print("Error Parsing"); return } + width = mazeArray[0].count + height = mazeArray.count + for line in mazeArray { + maze.append(Array(line)) + } + + func newEntity(type kind: State, at loc: (Int, Int)) { + var power = 3 + if kind == .Elf { + power = 12 + } + entities.append(Entity(kind: kind, loc: GridPoint(X: loc.0, Y: loc.1), power: power)) + } + + for _ in 0.. 0 { + entityDict[entities[index].loc] = index + } + } + } + + // Sort the entities (reading order) and repopulate the nummaze map + func updateMaze() { + for j in 0.. 0 { + switch entity.kind { + case .Elf: nummaze[entity.loc.Y][entity.loc.X] = .Elf + case .Goblin: nummaze[entity.loc.Y][entity.loc.X] = .Goblin + default: break + } + } + } + } + + + func printMaze(with maze: [[Int]]) { + let width = maze[0].count + let height = maze.count + + for j in 0.. (dist: Int, dir: GridPoint) { + var retVal = (dist: -1, dir: GridPoint(X:0, Y:0)) + var distMaze: [[Int]] = [] + for row in nummaze { + distMaze.append(row.map { if $0.rawValue > 0 { return -$0.rawValue } else { return $0.rawValue } }) + } + func testAndQueue(for point: GridPoint, on wave: Int, in queue: inout [GridPoint]) { + if distMaze[point.Y][point.X] == 0 { + distMaze[point.Y][point.X] = wave + queue.append(point) + } + } + + var wave = 1 + var done = false + var queue: [GridPoint] = [origin] + distMaze[dest.Y][dest.X] = 0 + while !done { + var newQueue: [GridPoint] = [] + for point in queue { + testAndQueue(for: point.up, on: wave, in: &newQueue) + testAndQueue(for: point.down, on: wave, in: &newQueue) + testAndQueue(for: point.left, on: wave, in: &newQueue) + testAndQueue(for: point.right, on: wave, in: &newQueue) + } + queue = newQueue + if queue.contains(dest) { + done = true + retVal.dist = distMaze[dest.Y][dest.X] + if wave == 1 { + retVal.dir = origin - dest + } + } + done = done || (newQueue.count == 0) + wave += 1 + } + // printMaze(with: distMaze) + + // Determin direction by locating the lowest distance count via reading-order + if retVal.dir == GridDir.none { + if distMaze[dest.up.Y][dest.up.X] == retVal.dist-1 { + retVal.dir = GridDir.up + } else if distMaze[dest.left.Y][dest.left.X] == retVal.dist-1 { + retVal.dir = GridDir.left + } else if distMaze[dest.right.Y][dest.right.X] == retVal.dist-1 { + retVal.dir = GridDir.right + } else if distMaze[dest.down.Y][dest.down.X] == retVal.dist-1 { + retVal.dir = GridDir.down + } + } + return retVal + } + + func findNearestTarget(for attacker: Entity) -> GridPoint { + var retVal = GridPoint(X: -1, Y: -1) + var list: [GridPoint : Bool] = [:] + func testAnAddToList(for point: GridPoint) { + if nummaze[point.Y][point.X] == .Open { + list[point] = true + } + } + + // Create a list of reachables + for entity in entities { + if entity.hitPts > 0 && entity.kind == enemy(of: attacker) { + testAnAddToList(for: entity.loc.up) + testAnAddToList(for: entity.loc.down) + testAnAddToList(for: entity.loc.left) + testAnAddToList(for: entity.loc.right) + } + } + let inRange = Array(list.keys) + var reachable: [GridPoint : Int] = [:] + + // Create a sorted list of Nearest targets from reachables + var minDist = width + height + for target in inRange { + let dist = distance(from: attacker.loc, to: target).dist + if dist > 0 { + minDist = min(dist, minDist) + reachable[target] = dist + } + } + var nearest = Array(reachable.filter { $0.value == minDist }.keys) + nearest.sort() + if nearest.count > 0 { + retVal = nearest[0] + } + return retVal + } + + func moveDirection(from entity: Entity, to dest: GridPoint) -> GridPoint { + // use the reverse map in 'distance' to come up with a preferred direction + var retVal = GridPoint(X:0,Y:0) + if dest != GridPoint(X: -1, Y: -1) { + let dist = distance(from: dest, to: entity.loc) + if dist.dist > 0 { + retVal = dist.dir + } + } + return retVal + } + + func round() -> Bool { + var done = false + // Only sort once per round + entities.sort() + for index in 0.. 0 { + done = takeTurn(with: index) + if done { + break + } + } + } + if !done { + rounds += 1 + } + return done + } + + func tabulateScore() -> Int { + // sum remaining hitPts + var sum = 0 + for entity in entities { + if entity.hitPts > 0 { + sum += entity.hitPts + } + } + return sum * rounds + } + + let watchIndex = -1 + + + func takeTurn(with entityIndex: Int) -> Bool { + //done test + let viableEnemy = entities.filter { $0.kind == enemy(of: entities[entityIndex]) && $0.hitPts > 0 } + guard viableEnemy.count > 0 else { return true } + + // entityDict must be updated for every action (move or attack) + updateEntityDict() + // Adjacent to target? + var indexOfAdjeacentTarget = adjacent(to: entityIndex) + if indexOfAdjeacentTarget == -1 { + // find nearest enemy + let nearest = findNearestTarget(for: entities[entityIndex]) + // determine direction to go + let direction = moveDirection(from: entities[entityIndex], to: nearest) + // print("nearest \(nearest), direction = \(direction)") + // move and update dict and map + entities[entityIndex].loc = entities[entityIndex].loc + direction + if entityIndex == watchIndex { + print("\(entities[entityIndex])") + print("nearest : \(nearest)") + print("direction : \(direction)") + } + updateEntityDict() + updateMaze() + } + // Check if we moved within range + indexOfAdjeacentTarget = adjacent(to: entityIndex) + if entityIndex == watchIndex && indexOfAdjeacentTarget > 0 { + print("adjacent Target : \(entities[indexOfAdjeacentTarget])") + } + if indexOfAdjeacentTarget != -1 { + attack(from: entities[entityIndex], to: indexOfAdjeacentTarget) + updateEntityDict() + updateMaze() + } + return false + } + + func enemy(of entity: Entity) -> State { + var retVal = State.Goblin + if entity.kind == .Goblin { + retVal = .Elf + } + return retVal + } + + // Return the index number of the target to attack (or -1 if no target) + func adjacent(to subjectIndex: Int) -> Int { + var retVal = -1 + var enemyDict: [GridPoint:Int] = [:] // Loc : HitPts + + func determineEnemyAndQueue(for loc: GridPoint) { + if nummaze[loc.Y][loc.X] == enemy(of: entities[subjectIndex]) { + updateEntityDict() + if let index = entityDict[loc] { + enemyDict[loc] = entities[index].hitPts + } + } + } + // create list of adjacent enemies + determineEnemyAndQueue(for: entities[subjectIndex].loc.up) + determineEnemyAndQueue(for: entities[subjectIndex].loc.left) + determineEnemyAndQueue(for: entities[subjectIndex].loc.right) + determineEnemyAndQueue(for: entities[subjectIndex].loc.down) + + // sort list by HitPoints, fewest to most order + if enemyDict.count > 0 { + // find min hit point + let minHP = enemyDict.min { a, b in a.value < b.value }?.value ?? -1 + // gather array of min-hitpoint enemies + let minDict = enemyDict.filter {$0.value == minHP} + // sort enemies by location in reading-order + let enemy = minDict.sorted(by: <) + // select the first one + retVal = entityDict[enemy[0].key]! + } + return retVal + } + + func attack(from attacker: Entity, to victimIndex: Int) { + entities[victimIndex].hitPts = entities[victimIndex].hitPts - attacker.power + // print("attacker power = \(attacker.power)") + if entities[victimIndex].hitPts <= 0 && entities[victimIndex].kind == .Elf { + print("Fail") + while true {} + } + } +} + + + + +class Day15: AOCDay { + lazy var tests: (() -> ()) = day15Tests + lazy var final: (() -> ()) = day15Final + + let testData1 = """ + ####### + #E..G.# + #...#.# + #.G.#G# + ####### + """ + + let testData2 = """ + ####### + #.E...# + #.....# + #...G.# + ####### + """ + + let testData3 = """ + ######### + #G.....G# + #...G...# + #...E...# + #G.....G# + #.......# + #.......# + #G..G..G# + ######### + """ + + let testData4 = """ + ######### + #G..G..G# + #.......# + #.......# + #G..E..G# + #.......# + #.......# + #G..G..G# + ######### + """ + + let testData5 = """ + ####### + #...G.# + #..G.G# + #.#.#G# + #...#E# + #.....# + ####### + """ + + let testData6 = """ + ####### + #G....# + #..G..# + #..EG.# + #..G..# + #...G.# + ####### + """ + + + let testData7 = """ + ####### + #.G...# + #...EG# + #.#.#G# + #..G#E# + #.....# + ####### + """ + +// Combat ends after 37 full rounds +// Elves win with 982 total hit points left +// Outcome: 37 * 982 = 36334 + let testData8 = """ + ####### + #G..#E# + #E#E.E# + #G.##.# + #...#E# + #...E.# + ####### + """ + +// Combat ends after 46 full rounds +// Elves win with 859 total hit points left +// Outcome: 46 * 859 = 39514 + let testData9 = """ + ####### + #E..EG# + #.#G.E# + #E.##E# + #G..#.# + #..E#.# + ####### + """ + +// Combat ends after 35 full rounds +// Goblins win with 793 total hit points left +// Outcome: 35 * 793 = 27755 + let testData10 = """ + ####### + #E.G#.# + #.#G..# + #G.#.G# + #G..#.# + #...E.# + ####### + """ + +// Combat ends after 54 full rounds +// Goblins win with 536 total hit points left +// Outcome: 54 * 536 = 28944 + let testData11 = """ + ####### + #.E...# + #.#..G# + #.###.# + #E#G#G# + #...#G# + ####### + """ + +// Combat ends after 20 full rounds +// Goblins win with 937 total hit points left +// Outcome: 20 * 937 = 18740 + let testData12 = """ + ######### + #G......# + #.E.#...# + #..##..G# + #...##..# + #...#...# + #.G...G.# + #.....G.# + ######### + """ + + func testInitFile() { + let bev = Beverage(withFile: "/home/peterr/AOC2018/Sources/AOC2018/data/day15.txt") + printMaze(with: bev) + } + + func testInitString() { + let bev = Beverage(withString: testData1) + printMaze(with: bev) + } + + func testEntities() { + let bev = Beverage(withString: testData1) + var myentities = bev.entities + func show(entities: [Entity]) { + var id = 0 + for entity in entities { + print("\(id) : \(entity)") + id += 1 + } + } + // show(entities: myentities) // Demonstrated that sorting works when force-read in the entities in reversse order + myentities.sort() + // show(entities: myentities) + } + + func testFindNearestTarget() { + var bev = Beverage(withString: testData1) + var nearest = bev.findNearestTarget(for: bev.entities[0]) + XCTAssertEqual(test: "testFindNearestTarget (Elf)", withExpression: (nearest == GridPoint(X: 3, Y: 1))) + nearest = bev.findNearestTarget(for: bev.entities[1]) + XCTAssertEqual(test: "testFindNearestTarget (Goblin 0)", withExpression: (nearest == GridPoint(X: 2, Y: 1))) + nearest = bev.findNearestTarget(for: bev.entities[2]) + XCTAssertEqual(test: "testFindNearestTarget (Goblin 1)", withExpression: (nearest == GridPoint(X: 2, Y: 1))) + nearest = bev.findNearestTarget(for: bev.entities[3]) + XCTAssertEqual(test: "testFindNearestTarget (Goblin 2)", withExpression: (nearest == GridPoint(X: -1, Y: -1))) + + bev = Beverage(withString: testData5) + nearest = bev.findNearestTarget(for: bev.entities[3]) + XCTAssertEqual(test: "testFindNearestTarget is Goblin", withExpression: (bev.entities[3].kind == .Goblin)) + XCTAssertEqual(test: "testFindNearestTarget is Elf", withExpression: (bev.entities[4].kind == .Elf)) + XCTAssertEqual(test: "testFindNearestTarget (Goblin)", withExpression: (nearest == GridPoint(X: 3, Y: 3))) + } + + func testDistance() { + let bev = Beverage(withString: testData1) + var dist = bev.distance(from: bev.entities[0].loc, to: GridPoint(X: 2, Y: 2)).dist + XCTAssertEqual(test: "testDistance (2, 2) = 2", withExpression: (dist == 2)) + dist = bev.distance(from: bev.entities[0].loc, to: GridPoint(X: 5, Y: 2)).dist + XCTAssertEqual(test: "testDistance (5, 2) = -1", withExpression: (dist == -1)) + dist = bev.distance(from: bev.entities[0].loc, to: GridPoint(X: 2, Y: 1)).dist + XCTAssertEqual(test: "testDistance (2, 1) = 0", withExpression: (dist == 1)) + dist = bev.distance(from: bev.entities[3].loc, to: GridPoint(X: 2, Y: 1)).dist + XCTAssertEqual(test: "testDistance last Goblin to` Elf", withExpression: (dist == -1)) + } + + func testMoveDirection() { + var bev = Beverage(withString: testData1) + var nearest = bev.findNearestTarget(for: bev.entities[0]) + var direction = bev.moveDirection(from: bev.entities[0], to: nearest) + XCTAssertEqual(test: "testMoveDirection testData1", withExpression: (direction == GridDir.right)) + bev = Beverage(withString: testData2) + nearest = bev.findNearestTarget(for: bev.entities[0]) + direction = bev.moveDirection(from: bev.entities[0], to: nearest) + XCTAssertEqual(test: "testMoveDirection testData2", withExpression: (direction == GridDir.right)) + + bev = Beverage(withString: testData5) + nearest = bev.findNearestTarget(for: bev.entities[1]) + direction = bev.moveDirection(from: bev.entities[1], to: nearest) + print("Nearest: \(nearest)") + print("Direction: \(direction)") + XCTAssertEqual(test: "testMoveDirection is Goblin", withExpression: (bev.entities[1].kind == .Goblin)) + XCTAssertEqual(test: "testMoveDirection is Elf", withExpression: (bev.entities[4].kind == .Elf)) + XCTAssertEqual(test: "testMoveDirection (nearest)", withExpression: (nearest == GridPoint(X: 5, Y: 5))) + XCTAssertEqual(test: "testMoveDirection (dir)", withExpression: (direction == GridDir.down)) + } + + func testAdjacent() { + var bev = Beverage(withString: testData3) + var adjacent = bev.adjacent(to: 3) + XCTAssertEqual(test: "testAdjacent is Elf", withExpression: (bev.entities[3].kind == .Elf)) + XCTAssertEqual(test: "testAdjacent is adjacent", withExpression: (adjacent == 2)) + bev = Beverage(withString: testData4) + adjacent = bev.adjacent(to: 4) + XCTAssertEqual(test: "testAdjacent is Elf", withExpression: (bev.entities[4].kind == .Elf)) + XCTAssertEqual(test: "testAdjacent is not adjacent", withExpression: (adjacent == -1)) + } + + func testTakeTurn() { + var bev = Beverage(withString: testData3) + XCTAssertEqual(test: "testTakeTurn is Elf", withExpression: (bev.entities[3].kind == .Elf)) + // printMaze(with: bev) + _ = bev.takeTurn(with: 3) + // printMaze(with: bev) + bev = Beverage(withString: testData4) + XCTAssertEqual(test: "testTakeTurn is Elf", withExpression: (bev.entities[4].kind == .Elf)) + // printMaze(with: bev) + _ = bev.takeTurn(with: 4) + // printMaze(with: bev) + } + + func testAttack() { + let bev = Beverage(withString: testData6) + // G.... 9 G.... 9 + // ..G.. 4 ..G.. 4 + // ..EG. 2 --> ..E.. + // ..G.. 2 ..G.. 2 + // ...G. 1 ...G. 1 + bev.entities[0].hitPts = 9 + bev.entities[1].hitPts = 4 + bev.entities[3].hitPts = 2 + bev.entities[4].hitPts = 2 + bev.entities[5].hitPts = 1 + printMaze(with: bev, withHP: true) + let indexOfAdjeacentTarget = bev.adjacent(to: 2) + if indexOfAdjeacentTarget != -1 { + bev.attack(from: bev.entities[2], to: indexOfAdjeacentTarget) + bev.updateEntityDict() + bev.updateMaze() + } + printMaze(with: bev, withHP: true) + } + + func testRound() { + let bev = Beverage(withString: testData4) + // printMaze(with: bev) + _ = bev.round() + // printMaze(with: bev) + _ = bev.round() + // printMaze(with: bev) + _ = bev.round() + printMaze(with: bev) + let adjacent = bev.adjacent(to: 4) + // print("adjacent=\(adjacent)") + // print("entity = \(bev.entities[adjacent])") + XCTAssertEqual(test: "testRound is Elf", withExpression: (bev.entities[4].kind == .Elf)) + XCTAssertEqual(test: "testRound attack entity 1", withExpression: (bev.entities[adjacent].loc == GridPoint(X: 4, Y: 2))) + } + + func testSampleGame() { + var bev = Beverage(withString: testData7) + // printMaze(with: bev, withHP: true) + while !bev.round() {} + // printMaze(with: bev, withHP: true) + var final = bev.tabulateScore() + // print("Final score = \(final)") + XCTAssertEqual(test: "testSampleGame 7", withExpression: (final == 27730)) + + bev = Beverage(withString: testData8) + // printMaze(with: bev, withHP: true) + while !bev.round() {} + // printMaze(with: bev, withHP: true) + final = bev.tabulateScore() + // print("Final score = \(final)") + XCTAssertEqual(test: "testSampleGame 8", withExpression: (final == 36334)) + + bev = Beverage(withString: testData9) + // printMaze(with: bev, withHP: true) + while !bev.round() {} + // printMaze(with: bev, withHP: true) + final = bev.tabulateScore() + // print("Final score = \(final)") + XCTAssertEqual(test: "testSampleGame 9", withExpression: (final == 39514)) + + bev = Beverage(withString: testData10) + // printMaze(with: bev, withHP: true) + while !bev.round() {} + // printMaze(with: bev, withHP: true) + final = bev.tabulateScore() + // print("Final score = \(final)") + XCTAssertEqual(test: "testSampleGame 10", withExpression: (final == 27755)) + + bev = Beverage(withString: testData11) + // printMaze(with: bev, withHP: true) + while !bev.round() {} + // printMaze(with: bev, withHP: true) + final = bev.tabulateScore() + // print("Final score = \(final)") + XCTAssertEqual(test: "testSampleGame 11", withExpression: (final == 28944)) + + bev = Beverage(withString: testData12) + // printMaze(with: bev, withHP: true) + while !bev.round() {} + // printMaze(with: bev, withHP: true) + final = bev.tabulateScore() + // print("Final score = \(final)") + XCTAssertEqual(test: "testSampleGame 12", withExpression: (final == 18740)) + + + // printMaze(with: bev, withHP: true) + // bev.round() + // printMaze(with: bev, withHP: true) + // bev.round() + // printMaze(with: bev, withHP: true) + // for _ in 2..<23 { + // bev.round() + // } + // printMaze(with: bev, withHP: true) + // bev.round() + // printMaze(with: bev, withHP: true) + // bev.round() + // printMaze(with: bev, withHP: true) + // bev.round() + // printMaze(with: bev, withHP: true) + // bev.round() + // printMaze(with: bev, withHP: true) + // bev.round() + // printMaze(with: bev, withHP: true) + // for _ in 28..<46 { + // bev.round() + // } + // printMaze(with: bev, withHP: true) + // bev.round() + // printMaze(with: bev, withHP: true) + } + + func printMaze(with bev: Beverage, withHP: Bool = false) { + let width = bev.nummaze[0].count + let height = bev.nummaze.count + func toEntityHP(i: Int, j: Int) -> Int { + var retVal = 0 + if let index = bev.entityDict[GridPoint(X: i, Y: j)] { + retVal = bev.entities[index].hitPts + } + return retVal + } + + print(" ROUND \(bev.rounds)") + for j in 0.. ())] = [] var allFinal: [(() -> ())] = [] @@ -26,6 +26,7 @@ allTests.append(Day11().tests) allTests.append(Day12().tests) allTests.append(Day13().tests) allTests.append(Day14().tests) +allTests.append(Day15().tests) // Compile list of Answers allFinal.append(Day01().final) @@ -42,6 +43,7 @@ allFinal.append(Day11().final) allFinal.append(Day12().final) allFinal.append(Day13().final) allFinal.append(Day14().final) +allFinal.append(Day15().final) if onlyOneDay > 0 { print("\nDay \(onlyOneDay)") diff --git a/Sources/AOC2018/tools.swift b/Sources/AOC2018/tools.swift index 1870f23..483a418 100644 --- a/Sources/AOC2018/tools.swift +++ b/Sources/AOC2018/tools.swift @@ -19,14 +19,23 @@ func == (tuple1:(T,T),tuple2:(T,T)) -> Bool return (tuple1.0 == tuple2.0) && (tuple1.1 == tuple2.1) } +struct GridDir { + static let none = GridPoint(X: 0, Y: 0) + static let up = GridPoint(X: 0, Y: -1) + static let down = GridPoint(X: 0, Y: 1) + static let left = GridPoint(X: -1, Y: 0) + static let right = GridPoint(X: 1, Y: 0) +} + struct GridPoint: Equatable, Comparable, Hashable { var X = 0 var Y = 0 - var hashValue: Int { - get { - return X.hashValue ^ Y.hashValue - } - } + var up: GridPoint { return GridPoint(X: self.X, Y: self.Y-1) } + var down: GridPoint { return GridPoint(X: self.X, Y: self.Y+1) } + var left: GridPoint { return GridPoint(X: self.X-1, Y: self.Y) } + var right: GridPoint {return GridPoint(X: self.X+1, Y: self.Y) } + + var hashValue: Int { return X.hashValue ^ Y.hashValue } static func == (lhs: GridPoint, rhs: GridPoint) -> Bool { return (lhs.X == rhs.X) && (lhs.Y == rhs.Y) @@ -35,6 +44,14 @@ struct GridPoint: Equatable, Comparable, Hashable { static func < (lhs: GridPoint, rhs: GridPoint) -> Bool { return lhs.Y == rhs.Y ? lhs.X < rhs.X : lhs.Y < rhs.Y } + + static func + (lhs: GridPoint, rhs: GridPoint) -> GridPoint { + return GridPoint(X: lhs.X + rhs.X, Y: lhs.Y + rhs.Y) + } + + static func - (lhs: GridPoint, rhs: GridPoint) -> GridPoint { + return GridPoint(X: lhs.X - rhs.X, Y: lhs.Y - rhs.Y) + } } struct Tools {