Skip to content

Instantly share code, notes, and snippets.

@daltonclaybrook
Last active May 8, 2022 18:10
Show Gist options
  • Save daltonclaybrook/bd68969bde638c62f577621b7f53f59f to your computer and use it in GitHub Desktop.
Save daltonclaybrook/bd68969bde638c62f577621b7f53f59f to your computer and use it in GitHub Desktop.
typealias Roll = Int
typealias Score = Int
enum FrameAttempt {
case strike
case spare(first: Roll)
case open(first: Roll, second: Roll)
case tenth(first: Roll, second: Roll, third: Roll?)
case inProgress(first: Roll, second: Roll?)
}
/// Calculate score based on list of frame attempts
func calculateCurrentScore(attempts: [FrameAttempt]) -> Score {
let allRolls = attempts.flatMap(\.rolls)
return calculateCurrentScore(allRolls: allRolls)
}
/// Calculate score based on all consecutive rolls
func calculateCurrentScore(allRolls: [Roll]) -> Score {
var frameScores: [Score] = []
var rollIndex = 0
while (rollIndex < allRolls.count && frameScores.count < 10) {
let frameFirstRoll = allRolls[rollIndex]
var frameScore = frameFirstRoll
rollIndex += 1
let tenthFrame = frameScores.count == 9
if (frameFirstRoll.isAllPins || tenthFrame) {
// strike or tenth frame — add the next two rolls
frameScore += allRolls.sumOf(offset: rollIndex, count: 2)
frameScores.append(frameScore)
continue
}
// Get the next roll, or zero if it hasn't happened yet
let frameNextRoll = allRolls[safe: rollIndex] ?? 0
frameScore += frameNextRoll
rollIndex += 1
if (frameScore.isAllPins) {
// spare - add the next one roll
frameScore += allRolls.sumOf(offset: rollIndex, count: 1)
}
frameScores.append(frameScore)
}
return frameScores.reduce(0, +)
}
// MARK: - Demo
let score1 = calculateCurrentScore(allRolls: [4, 6, 10, 3, 5, 7])
print(score1) // 53
let perfectGame: [FrameAttempt] = [
.strike, .strike, .strike, .strike, .strike, .strike, .strike, .strike, .strike,
.tenth(first: 10, second: 10, third: 10)
]
let score2 = calculateCurrentScore(attempts: perfectGame)
print(score2) // 300
// MARK: - Helper extensions
extension FrameAttempt {
var rolls: [Roll] {
switch self {
case .strike:
return [10]
case .spare(let first):
return [first, 10 - first]
case .open(let first, let second):
return [first, second]
case .tenth(let first, let second, let third):
return [first, second, third].compactMap { $0 }
case .inProgress(let first, let second):
return [first, second].compactMap { $0 }
}
}
}
extension Array where Element == Roll {
/// Return the sum of `count` consecutive elements starting with the element at index `offset`
func sumOf(offset: Index, count: Int) -> Roll {
return (offset..<(offset + count)).reduce(0) { result, rollIndex in
result + (self[safe: rollIndex] ?? 0)
}
}
subscript(safe index: Int) -> Element? {
guard count > index else { return nil }
return self[index]
}
}
extension Roll {
var isAllPins: Bool {
self == 10
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment