Created
September 4, 2020 12:08
-
-
Save novinfard/d04510ee1856f5143de8345c7ac2e679 to your computer and use it in GitHub Desktop.
[Quiz State Machine in Swift]
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
import Foundation | |
struct QuizQuestionItem: Equatable { | |
var allAnswers: [QuizAnswerItem] | |
var rightAnswer: QuizAnswerItem | |
} | |
struct QuizAnswerItem: Equatable { | |
} | |
// MARK: - Quiz State Machine Abstraction | |
enum QuizMachineState { | |
case initial | |
case question(QuizQuestionItem) | |
case end | |
} | |
enum QuizEvents { | |
case goToNext(answer: QuizAnswerItem) | |
case goBack | |
} | |
enum QuizEventResponse { | |
case successful | |
case failed(QuizError) | |
} | |
enum QuizError: Error { | |
case invalidTransition | |
case wrongAnswer | |
} | |
protocol QuizStateMachine { | |
var questions: [QuizQuestionItem] { get } | |
var state: QuizMachineState { get } | |
func progress(state: QuizMachineState, event: QuizEvents) -> (newState: QuizMachineState, response: QuizEventResponse) | |
} | |
// MARK: - Quiz State Machine Implementation | |
class QuizStateMachineImplementation { | |
var state: QuizMachineState | |
var questions: [QuizQuestionItem] | |
init(questions: [QuizQuestionItem]) { | |
self.questions = questions | |
self.state = .initial | |
} | |
func progress(state: QuizMachineState, event: QuizEvents) -> (newState: QuizMachineState, response: QuizEventResponse) { | |
var output: (newState: QuizMachineState, response: QuizEventResponse) | |
switch event { | |
case .goToNext(let answer): | |
switch state { | |
case .initial: | |
guard let firstQuestion = questions.first else { | |
output = (.initial, .failed(QuizError.invalidTransition)) | |
break | |
} | |
output = (.question(firstQuestion), .successful) | |
case .question(let question): | |
if question.rightAnswer == answer { | |
guard let nextQuestion = questions.element(after: question) else { | |
output = (.end, .successful) | |
break | |
} | |
output = (.question(nextQuestion), .successful) | |
} else { | |
output = (.question(question), .failed(QuizError.wrongAnswer)) | |
} | |
case .end: | |
output = (state, .failed(QuizError.invalidTransition)) | |
} | |
case .goBack: | |
switch state { | |
case .initial: | |
output = (state, .failed(QuizError.invalidTransition)) | |
case .question(let question): | |
guard let previousQuestion = questions.element(before: question) else { | |
output = (state, .failed(QuizError.invalidTransition)) | |
break | |
} | |
output = (.question(previousQuestion), .successful) | |
case .end: | |
guard let previousQuestion = questions.last else { | |
output = (state, .failed(QuizError.invalidTransition)) | |
break | |
} | |
output = (.question(previousQuestion), .successful) | |
} | |
} | |
self.state = output.newState | |
return output | |
} | |
} | |
// MARK: - Array Helpers | |
extension Array where Element: Equatable { | |
func element(after element: Element) -> Element? { | |
guard let index = self.firstIndex(of: element) else { return nil } | |
return self[safe: index + 1] | |
} | |
func element(before element: Element) -> Element? { | |
guard let index = self.firstIndex(of: element) else { return nil } | |
return self[safe: index - 1] | |
} | |
} | |
extension Array { | |
public subscript(safe index: Int) -> Element? { | |
return indices.contains(index) ? self[index] : nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment