ohdair / Sudoku

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Sudoku

MVC패턴으로 작성된 코드를 RxSwift 적용하여 MVVM으로 리팩터링한 스도쿠 게임

게임 화면

홈 화면 게임 화면 종료 화면

UI 설계 및 플로우에 따른 다이어그램

스크린샷 2024-02-25 오후 5 16 44

메인화면

func viewWillAppear() 뷰가 보여지기 전에 UserDefault에 저장된 게임 데이터가 있다면 데이터를 불러와서 이어하기 버튼이 보여지도록 설정 데이터가 없다면 시작하기 버튼만 존재

RxCocoa를 사용해 tap 제스처가 발생하면 GameViewController로 넘어갈 수 있도록 bind

private func bind() {
    newGameButton.rx.tap
        .asDriver()
        .drive { _ in
            let gameViewController = GameViewController()
            self.navigationController?.pushViewController(gameViewController, animated: true)
        }
        .disposed(by: disposeBag)

    continueGameButton.rx.tap
        .asDriver()
        .drive { _ in
            let gameViewModel = GameViewModel(sudoku: self.savedGame!)
            let gameViewController = GameViewController(viewModel: gameViewModel)

            self.navigationController?.pushViewController(gameViewController, animated: true)
        }
        .disposed(by: disposeBag)
}

게임화면

화면 내 많은 버튼 및 뷰가 존재하며 각각 뷰에서 필요한 정보들을 전달

  1. 일시정지 및 뒤로가기
  2. 게임의 정보들을 보여주는 뷰
  3. 9 * 9로 이뤄진 보드 뷰
  4. 게임의 데이터를 조정 및 입력을 조정하는 버튼들
  5. 보드 판 내에 입력할 수 있도록 하는 숫자 버튼들

Input & Output 디자인 패턴을 사용하여 ViewModel 생성

InformationViewModel

게임 진행에 사용된 시간 측정은 Driver<Int>.interval을 사용

final class InformationViewModel: ViewModelType {

    struct Input {
        var difficulty: Observable<GameDifficulty>
        var mistake: Observable<Int>
        var mistakeTrigger: Observable<Void>
        var time: Observable<TimeInterval>
        var timerTrigger: Driver<Void>
    }

    struct Output {
        var difficulty: Driver<String>
        var mistake: Driver<Int>
        var time: Driver<TimeInterval>
    }

    private let time = BehaviorRelay<TimeInterval>(value: 0)
    private let isOnTimer = BehaviorRelay<Bool>(value: false)

    func transform(input: Input) -> Output {
        // ...
        Driver<Int>
            .interval(.seconds(1))
            .filter { _ in self.isOnTimer.value }
            .drive { _ in
                self.updateTimer()
            }
            .disposed(by: disposeBag)
        // ...
    }
}

BoardViewModel

보드판에서 고려할 대상들

  1. 보드판에 입력된 커서 유/무
  2. 메모의 기능 On/Off
  3. 입력받은 숫자
  4. 커서에 따른 연관된 보드판 내 셀들
  5. 입력받은 숫자와 연관된 보드판 내 셀들
  6. mistake가 되는지 판단

보드의 정보가 바뀔 때, 보드판의 커서를 선택에 따른 연관된 보드판을 반응형으로 표현하기 위해 RxSwift의 withLatestFromcompactMap을 사용하여 값이 존재할 때에만 셀의 배경 및 텍스트 변경할 수 있도록 구독

input.board
    .withLatestFrom(cursor)
    .compactMap { $0 }
    .subscribe { ... }

Output으로 Board 정보를 내보낼 때, 연관된 지우기 기능으로 셀을 지우거나 메모를 입력 그리고 숫자를 입력하는 경우 하나의 데이터로 묶어서 값을 내보내기 위해 Driver.merge를 사용

Output(
    board: Driver.merge(updatedBoardToErase, updatedBoardToMemo, updatedBoardToNumber)
    // ...
)

정보들의 연관관계가 많은만큼 filtercompactMap와 같이 데이터의 변경 등을 확인하여 동작되도록 설정

AbilityViewModel

보드판의 정보를 기록하는 Stack 형식으로 구현하여 뒤로가기 기능을 구현

private func pushBoard(_ board: Board) { ... }

private func undoBoard() -> Board { ... }

private func reset() { ... }

About


Languages

Language:Swift 100.0%