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