forked from kylebshr/chain-heal-swift
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ChainInfo.swift
234 lines (184 loc) · 8.28 KB
/
ChainInfo.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
//
// ChainInfo.swift
// chain-heal
//
// Created by Kyle Bashour on 12/7/15.
// Copyright © 2015 Kyle Bashour. All rights reserved.
//
#if os(Linux)
import Glibc
#endif
import Foundation
/// ChainInfo is the heart of the program.
/// It's responsible for parsing all the relevant information, and performing the search for the best path.
class ChainInfo {
let initialRange: Int
let jumpRange: Int
let numberOfJumps: Int
let initialPower: Int
let powerReduction: Double
private var bestHealing = 0
private var players = [Player]()
private var bestPath = [Player]()
private var urgosa: Player?
/**
Initialize a ChainInfo object with given values (not used in the current implementation)
- parameter initialRange: The initial range that Urgosa can cast the healing spell
- parameter jumpRange: The range the healing spell can jump after the intial cast
- parameter numberOfJumps: The number of times the spell can jump
- parameter initialPower: The initial healing power of the spell
- parameter powerReduction: The amount by which the healing power is reduced each jump
- returns: A fully initialized ChainInfo object
*/
init(initialRange: Int, jumpRange: Int, numberOfJumps: Int, initialPower: Int, powerReduction: Double) {
self.initialRange = initialRange
self.jumpRange = jumpRange
self.numberOfJumps = numberOfJumps
self.initialPower = initialPower
self.powerReduction = powerReduction
}
/**
Parse arguments from an array with thorough error checking, and create a ChainInfo item from them.
- parameter args: An array of strings for parsing into a ChainInfo struct
- returns: A fully initialized ChainInfo object created from the given array
*/
init(args: [String]) {
// This could, of course, be far more concise, but I wanted to output specific errors
// Check that there are enough elements to make a ChainInfo
guard args.count >= 5 else {
fatalError("Not enough arguments")
}
// Validate all of the input
guard let initialRange = Int(args[1]) else {
fatalError("Initial range must be an Int")
}
guard let jumpRange = Int(args[2]) else {
fatalError("Jump range must be an Int")
}
guard let numberOfJumps = Int(args[3]) else {
fatalError("Number of jumps must be an Int")
}
guard let initialPower = Int(args[4]) else {
fatalError("Initial power must be an Int")
}
guard let powerReduction = Double(args[5]) else {
fatalError("Power reduction must be a Double")
}
self.initialRange = initialRange
self.jumpRange = jumpRange
self.numberOfJumps = numberOfJumps
self.initialPower = initialPower
self.powerReduction = powerReduction
}
/**
Calculate the power left based on the given hop
- parameter hop: The current hope we wish to know the power for
- returns: The potential power for healing after on the given hop
*/
func powerForHop(hop: Int) -> Int {
// Create a double var for the calculations
var potential = Double(initialPower)
// Calculate the how much power we've lost
for _ in 1..<hop {
potential = potential * (1 - powerReduction)
}
// Use rint (as specified in the original lab) to return an Int value
return Int(rint(potential))
}
/**
Parse arguments from standard in into player objects.
*/
func parsePlayers() {
// Remove all current players
players.removeAll()
var lineNumber = 0
// Read in all players from std in
while let line = readLine() {
lineNumber += 1
// Split the line into the components specified in the lab (x, y, curPP, maxPP, name)
let line = line.characters.split{$0 == " "}.map(String.init)
// Grab all the parameters that need casting/checking, and also check that we have all 5 arguments
guard let x = Int(line[0]), y = Int(line[1]), currentPP = Int(line[2]), maxPP = Int(line[3]) where line.count == 5 else {
fatalError("Invalid player at line number: \(lineNumber)")
}
// Create a point, a name and player, then append it to the player array
let position = Point(x: x, y: y)
let name = line[4]
let player = Player(maxPP: maxPP, currentPP: currentPP, position: position, name: name)
players.append(player)
// Save urgosa for casting the initial spell
if player.name == "Urgosa_the_Healing_Shaman" {
urgosa = player
}
}
}
/**
Create the adjacency relationships for every player, based on the jump range of the spell
*/
func createAdjacentArrays() {
// Loop through the players and create their adjacent arrays
for player in players {
// Filter all the players based on their distance to the player
player.adjacentPlayers = players.filter {
$0.position.distanceTo(player.position) <= Double(jumpRange)
}
}
}
/**
Perform depth first search on the players to find the path with the most healing.
- returns: Returns a tuple with the best path and the score (healing) of that path
*/
func findBestPath() -> (path: [Player], healing: Int) {
// Make sure we have urgosa
guard let urgosa = urgosa else { fatalError("No player named Urgosa_the_Healing_Shaman was given") }
// Loop through all players, and if they're in urgosa's range, "cast the spell" (do the dfs)
for player in players {
if player.position.distanceTo(urgosa.position) <= Double(chainInfo.initialRange) {
dfs(player, hop: 1, totalHealing: 0)
}
}
return (bestPath, bestHealing)
}
/**
Perform the depth first search, starting on the given player.
- parameter player: A player for the current search
- parameter hop: The current number of times the spell has jumped
- parameter totalHealing: The current amount of healing
*/
func dfs(player: Player, hop: Int, totalHealing: Int) {
// Base case: we've either visited this player, or the spell can't jump anymore
if (player.visited) || (hop > chainInfo.numberOfJumps) {
return
}
// Add the healing for the current jump to the current total healing
let totalHealing = totalHealing + player.heal(chainInfo.powerForHop(hop))
// If the total is better than our best healing, save the path and score
if totalHealing > bestHealing {
bestHealing = totalHealing
bestPath.removeAll()
var bestPlayer: Player! = player
// Loop through all the previousPlayers, saving the current path
// (I bet there's a better way to do this, couldn't get my repeat-while working though)
while bestPlayer != nil {
bestPath.insert(bestPlayer, atIndex: 0)
bestPlayer.bestHealing = bestPlayer.healing
bestPlayer = bestPlayer.previousPlayer
}
}
// Mark this player as visited
player.visited = true
// Loop through all adjacent, unvisited players, calling dfs on them
for adjacentPlayer in player.adjacentPlayers {
if !adjacentPlayer.visited {
// Set the previousPlayer to current player
adjacentPlayer.previousPlayer = player
// Call the dfs
dfs(adjacentPlayer, hop: hop + 1, totalHealing: totalHealing)
// Unset the previousPlayer
adjacentPlayer.previousPlayer = nil
}
}
// Unset visited
player.visited = false
}
}