parent
14c93b8482
commit
5531412a55
@ -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.####....#
|
||||
####..#######.##.##########...##
|
||||
####..######################.###
|
||||
################################
|
||||
@ -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..<height {
|
||||
nummaze.append(Array(repeating: .Wall, count: width))
|
||||
}
|
||||
|
||||
for j in 0..<height {
|
||||
for i in 0..<width {
|
||||
switch maze[j][i] {
|
||||
case "#": break // we initialized this grid to all "walls"
|
||||
case ".": nummaze[j][i] = .Open
|
||||
case "G": nummaze[j][i] = .Goblin
|
||||
newEntity(type: .Goblin,at: (i, j))
|
||||
case "E": nummaze[j][i] = .Elf
|
||||
newEntity(type: .Elf, at: (i, j))
|
||||
default: nummaze[j][i] = .Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
updateEntityDict()
|
||||
entities.sort()
|
||||
}
|
||||
|
||||
func updateEntityDict() {
|
||||
entityDict = [:]
|
||||
for index in 0..<entities.count {
|
||||
if entities[index].hitPts > 0 {
|
||||
entityDict[entities[index].loc] = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the entities (reading order) and repopulate the nummaze map
|
||||
func updateMaze() {
|
||||
for j in 0..<height {
|
||||
for i in 0..<width {
|
||||
if nummaze[j][i] == .Elf || nummaze[j][i] == .Goblin {
|
||||
nummaze[j][i] = .Open
|
||||
}
|
||||
}
|
||||
}
|
||||
for entity in entities {
|
||||
if entity.hitPts > 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..<height {
|
||||
for i in 0..<width {
|
||||
switch maze[j][i] {
|
||||
case -4: print("?", terminator: "")
|
||||
case -3: print("#", terminator: "")
|
||||
case -2: print("G", terminator: "")
|
||||
case -1: print("E", terminator: "")
|
||||
case 0: print(".", terminator: "")
|
||||
default: print("\(maze[j][i])", terminator: "")
|
||||
}
|
||||
}
|
||||
print("")
|
||||
}
|
||||
print("")
|
||||
}
|
||||
|
||||
// Return the distance and the preferred direction (direction only valid by reversing the request; i.e. swapping orig and dest)
|
||||
func distance(from origin: GridPoint, to dest: GridPoint) -> (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..<entities.count {
|
||||
if entities[index].hitPts > 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..<height {
|
||||
for i in 0..<width {
|
||||
switch bev.nummaze[j][i] {
|
||||
case .Wall: print(" # ", terminator: "")
|
||||
case .Unknown: print(" ? ", terminator: "")
|
||||
case .Open: print(" . ", terminator: "")
|
||||
case .Goblin: withHP ? print("\u{001B}[0;31m\(String(format: "%03d", toEntityHP(i:i, j:j)))\u{001B}[0;37m", terminator: "") : print(" G ", terminator: "")
|
||||
case .Elf: withHP ? print("\u{001B}[0;32m\(String(format: "%03d", toEntityHP(i:i, j:j)))\u{001B}[0;37m", terminator: "") : print(" E ", terminator: "")
|
||||
}
|
||||
}
|
||||
print("")
|
||||
}
|
||||
print("")
|
||||
}
|
||||
|
||||
|
||||
func day15Tests() {
|
||||
// testInitFile()
|
||||
// testInitString()
|
||||
// testEntities()
|
||||
// testDistance()
|
||||
// testMoveDirection()
|
||||
// testFindNearestTarget()
|
||||
// testAdjacent()
|
||||
// testTakeTurn()
|
||||
// testRound()
|
||||
// testAttack()
|
||||
// testSampleGame()
|
||||
}
|
||||
|
||||
func day15Final() {
|
||||
let retVal = "None"
|
||||
let bev = Beverage(withFile: "/home/peterr/AOC2018/Sources/AOC2018/data/day15.txt")
|
||||
printMaze(with: bev, withHP: true)
|
||||
while !bev.round() {
|
||||
printMaze(with: bev, withHP: true)
|
||||
}
|
||||
// printMaze(with: bev, withHP: true)
|
||||
let final = bev.tabulateScore()
|
||||
|
||||
print("Answer to part 1 is: \(final)")
|
||||
print("Answer to part 2 is: \(retVal)")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue