Last active
May 8, 2022 18:10
-
-
Save daltonclaybrook/bd68969bde638c62f577621b7f53f59f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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