Skip to content

Instantly share code, notes, and snippets.

@novinfard
Created September 4, 2020 12:08
Show Gist options
  • Save novinfard/d04510ee1856f5143de8345c7ac2e679 to your computer and use it in GitHub Desktop.
Save novinfard/d04510ee1856f5143de8345c7ac2e679 to your computer and use it in GitHub Desktop.
[Quiz State Machine in Swift]
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