+ }
+
+ return result
+}
+
+func parseOpportunities(_ text: String, vertices: [String: CGPoint]) -> [Opportunity] {
+
+ var result: [Opportunity] = []
+ let regex = try! NSRegularExpression(pattern: opportunityPattern, options: .caseInsensitive)
+
+ let lines = text.split(whereSeparator: \.isNewline)
+
+ for (index, line) in lines.enumerated() {
+ let range = NSRange(location: 0, length: line.utf16.count)
+ let matches = regex.matches(in: String(line), options: [], range: range)
+
+ if matches.count > 0 && matches[0].numberOfRanges == 4 {
+
+ let match = matches[0]
+ let multiplier = CGFloat(
+ String(line[Range(match.range(at: 2), in: line)!]) == "-" ? -1.0 : 1.0)
+ let vertex = String(line[Range(match.range(at: 1), in: line)!])
+ let opportunityString = String(line[Range(match.range(at: 3), in: line)!])
+ let opportunity = CGFloat(
+ truncating: NumberFormatter().number(from: opportunityString) ?? 0.0)
+
+ if let origin = vertices[vertex] {
+ let destination = CGPoint(x: origin.x + opportunity * multiplier, y: origin.y)
+ result.append(Opportunity(id: index, origin: origin, destination: destination))
+ }
+ }
+ }
+
+ return result
+}
+
+func parseBlockers(_ text: String, vertices: [String: CGPoint]) -> [Blocker] {
+
+ var result: [Blocker] = []
+ let regex = try! NSRegularExpression(pattern: blockerPattern, options: .caseInsensitive)
+
+ let lines = text.split(whereSeparator: \.isNewline)
+
+ for (index, line) in lines.enumerated() {
+ let range = NSRange(location: 0, length: line.utf16.count)
+ let matches = regex.matches(in: String(line), options: [], range: range)
+
+ if matches.count > 0 && matches[0].numberOfRanges == 2 {
+
+ let match = matches[0]
+ let vertexA = String(line[Range(match.range(at: 1), in: line)!])
+
+ if let position = vertices[vertexA] {
+ result.append(Blocker(id: index, position: position))
+ }
+ }
+ }
+
+ return result
+}
+
+func parseStages(_ text: String) -> [CGFloat] {
+
+ var result = defaultDimensions
+ let regex = try! NSRegularExpression(pattern: stagePattern, options: .caseInsensitive)
+
+ let lines = text.split(whereSeparator: \.isNewline)
+
+ for line in lines {
+ let range = NSRange(location: 0, length: line.utf16.count)
+ let matches = regex.matches(in: String(line), options: [], range: range)
+
+ if matches.count > 0 && matches[0].numberOfRanges == 3 {
+
+ let match = matches[0]
+ let stage = String(line[Range(match.range(at: 1), in: line)!])
+ let dimensionsString = String(line[Range(match.range(at: 2), in: line)!])
+ let dimensions = CGFloat(truncating: NumberFormatter().number(from: dimensionsString) ?? 0.0)
+
+ result[stage.count - 1] = dimensions
+ }
+ }
+
+ return result