GithubHelp home page GithubHelp logo

jinios / swift-cardgameapp Goto Github PK

View Code? Open in Web Editor NEW

This project forked from code-squad/swift-cardgameapp

0.0 1.0 0.0 12.53 MB

솔리테어 카드게임앱 - 코드스쿼드 미션 (2018.04 - 2018.05)

Swift 100.00%

swift-cardgameapp's Introduction

완성화면

  • 게임 성공 화면

주요 기능

  • 솔리테어 카드게임을 구현한다.
  • 더블탭과 드래그로 카드를 옮길 수 있다.
  • 디바이스를 흔들면 (Shake Gesture)게임이 새로 시작된다.
  • 게임을 완료하면 안내메시지를 표시한다.

사용한 기술

  • Custom View, Gesture Recognizer, View Animation, UIAlertController,

설계

ViewModel 활용: 기존의 MVC를 유지하되, View를 Passive하게 만들고 View Model담당 객체를 추가

  • 이 프로젝트에서 가장 깊이 경험했던 부분은 복잡한 뷰 계층구조에서 발생하는 이벤트 감지와 처리를 위해 최대한 효과적으로 로직을 분리하는 것이었습니다.
  • 따라서 복잡한 계층 구조를 가진 객체의 역할을 분리하고, 최대한 Passive한 View를 만들어서 View Model만으로도 게임 로직이 성립되도록 구현했습니다.
  • 또한 이벤트 수신과 처리에 대한 결과를 반영하기위한 View update flow의 단방향성에 신경써서 작업하였습니다.

주요 객체 역할분담

  • 카드의 위치에 따라 Foundation, CardDeck, CardStack으로 객체 역할 구분
  • 카드게임 전체를 관할하는 CardGameManager 클래스를 Singleton객체로 설계하여 하나의 앱 당 하나의 CardGameManager만 존재하도록 구현하고, 게임이 재시작됐을때 새롭게 초기화된 게임매니저 객체를 할당

Model

  • CardGameManager: 카드게임 전체를 관할하는 모든 모델들의 최상위모델
  • Card: 카드한장을 나타내는 객체
  • CardDeck: 52장의 카드를 모아두는 카드덱
  • CardStack: 필드에 놓여진 카드 스택 하나

View - ViewModel

  • DeckManager - CardDeckView: CardDeck에 있는 카드뷰들을 담당
  • WholeStackManager - CardStacksView: 전체 CardStack 7개를 담당
  • StackManager - OneStack: CardStack 1개를 담당
  • FoundationManager - FoundationView: Foundation 담당 (카드가 순서대로 맞춰져서 쌓이는 곳)

Others

  • FrameCalculator: 각 서브뷰에서 수신되는 이벤트(DoubleTap, PanGesture)에 따른 카드뷰들의 위치를 계산하는 객체
  • (카드게임앱은 하나의 루트뷰를 Deck, Foundation, Stack의 세 영역으로 나누고 프레임을 계산해서 서브뷰를 띄운 방식으로 구현함. 따라서 ViewController가 제스처를 수신하고 이벤트를 처리할때 복잡한 프레임 계산을 담당하는 객체가 필요했음.)

학습내용

Custom View와 UIView의 생성자

사용자의 이벤트가 인식되는 구조

Handling UIKit Gestures 번역한 것 블로그에 정리

  1. 사용자는 디바이스에서 특정 액션을 취함 (터치, 줌 등)
  2. 그 액션에 해당하는 이벤트가 시스템에 의해 생성, UIKit에서 생성한 port를 통해 앱에 전달
  3. 이벤트들은 앱 내부적으로 queue에 저장(FIFO)
  4. UIApplication객체가 가장 먼저 이 이벤트를 받아서 어떤 동작이 취해질 지 결정
  • 터치 이벤트의 경우 main window객체가 인식하고 window객체가 다시 터치가 발생한 view로 이벤트를 전달함
  • 다른 이벤트들도 다양한 app객체에 따라 조금씩 다르게 동작

주로 발생되는 이벤트 처리

  • Touch이벤트: 터치 이벤트가 발생한 view객체 로 전달
    • view는 응답을 할 줄 아는(Responder) 객체이므로 터치 이벤트가 발생한 뷰 객체로 전달됨. 만약 해당 뷰에서 처리되지 않는 터치이벤트는 Responder chain을 따라 계속 내려가게됨
  • Remote control / Shake motion events: First responder object객체
    • 미디어콘텐츠의 재생이나 되감기 등과같은 remote control이벤트는 주로 헤드폰같은 악세사리에서 발생
  • Redraw: 업데이트가 필요한 객체
    • Redraw 이벤트는 이벤트 객체를 갖지는 않고, 단순히 업데이트가 필요한 view객체에 요청
  • 참고, 이벤트 소스 종류
    • 입력소스(input source): 다른 thread나 어플리케이션에서 전달되는 메시지 이벤트(비동기식)
    • 타이머소스(timer source): 예정시간이나 반복수행간격에 따라 발생하는 이벤트(동기식)
  • Touch나 Remote Control 같은 이벤트는 앱의 Responder객체를 통해 처리된다.
  • Responder객체는 앱의 모든 곳에 존재
    • UIApplication, 커스텀 view객체, ViewController객체 모두 Responder객체에 해당된다.

Touches, Presses, and Gestures UIResponder객체는 앱의 이벤트를 핸들링한다. 사용자로부터 입력되는 터치나 제스쳐 이벤트는 UIEvent객체로써 앱과 연결된다.

swift-cardgameapp's People

Contributors

godrm avatar jinios avatar

Watchers

 avatar

swift-cardgameapp's Issues

MoveInfo에서 view가 Stack인경우 origin이 다 (0,0)인 문제

각 OneStack뷰들이 따로 나눠져있기때문에 7개의 OneStack뷰 모두 frame.origin은 (0,0)이며, PositionX enum으로 계산할 수 없다.
따라서 아래의 stackColumn() 메소드에서 column이 nil로 리턴되는 문제까지 연쇄적으로 일어난다.

// FrameCalculator.swift
    func stackColumn(originX: CGFloat) -> Int? {
        for x in PositionX.allValues {
            guard x.value == originX else { continue }
            return x.hashValue
        }
        return nil
    }

MoveInfo의 view가 Stack인경우, 드래그액션이 시작된 OneStack이 어떤 스택인지(origin.x값으로 알 수 있도록) 계산하는 로직을 추가한다.

DeckView에 카드가 드래그되면 카드뷰가 없어지는 문제

카드덱과 관련된 위치에 카드뷰가 드래그되면 카드뷰가 사라지는 문제

  • 카드덱에 있던 카드뷰가 다른 스택, 혹은 파운데이션으로 으로 옮겨졌는데 rule에 맞지 않아 리턴되는 경우
  • 스택에 있던 카드뷰가 카드덱으로 옮겨졌는데 다시 스택으로 돌아오는 경우
  • 이때 카드모델은 그대로 존재하지만 카드뷰만 없어지는 현상임
  • 드래그 제스쳐가 끝나거나 시작한 위치가 deck인 경우 서브뷰들을 모델대로 업데이트하는 로직이 없어서 발생하는듯

DeckManageable과 StackManageable을 합치고 CardGameDelegate클래스로 구현

<이유>

  • 카드게임이 진행되기위해서는 카드 52장이 있는 하나의 카드덱으로 stack과 deck을 구성해서 게임을해야한다.
  • 지금처럼 DeckManageable(CardDeckDelegate)와 StackManageable(CardGameDelegate)을 에서 따로 cardDeck을 가지고있으면 한 게임에 두 카드덱이 존재하게된다.
  • 또한 현재 DeckManageable이 가진 기능이 많지 않으므로 따로 관리할 필요성이 없어서 합쳐서 게임을 총괄적으로 진행하는 모델인 CardGameDelegate하나로만 게임을 관리하기로했다.

Stack카드들이 드래그로 움직였을때 도착지점(y 위치)

  • 출발 - 도착 지점이 stack - stack인 경우 y포지션이PositionY.upper.value
  • 이때 움직이는 movableCards의 마지막카드기준으로 PositionY.upper.value가 도착지점이 되어버려서 카드스택자체가 위로 올라갔다가 다시 그려짐
  • movableCards의 첫번째 카드 기준으로 y position 잡기
    image

1~7장의 카드가 있는 CardStack 표시

  • CardDeck 객체에서 랜덤으로 카드를 섞고, 출력 화면처럼 카드스택 형태로 보이도록 개선한다.
  • 카드스택을 관리하는 모델 객체를 설계한다.
  • 각 스택의 맨위의 카드만 앞카드로 뒤집는다.
  • 앱에서 Shake 이벤트를 발생하면 랜덤 카드를 다시 섞고 처음 상태로 다시 그리도록 구현한다.

availableFrame()의 리턴값이 CGPoint(x:0.0, y:0.0)인 문제

  • drag가 끝난지점부터 toPoint까지 animate시키는 부분에서 카드가 (0,0)위치로 animate됐다가 돌아오는 현상.
  • 원인1 : FrameCalculator.availableFrame()에서 toPoint가 (0,0)으로 리턴
  • 원인2 : ViewController의 toInfo()에서 MoveInfo의 index가 nil로 리턴

* toPoint: cardStack(or foundation)까지 카드가 옮겨질 딱 맞는 위치

CGRect의 contains() 기능 확장

해당 CGRect내부에 CGPoint가 있는지 확인하는 메소드 구현하기

  • 해당 CGPoint가 어떤 뷰(CardDeck, Stacks, Foundation view중 1)인지 리턴

Step3 PR 코멘트

  • CardStack 속성 Private으로 지정하고 Stack을 ViewController에서 사용할때 관리하는 객체 분담해보기
From JK:
- private 에서 다시 public으로 바뀐건가요? 여기에 접근하는 경우가 있다면 메소드를 만들어서 해결하는 방법을 고려해보세요.
- 여기서 사용하는 stacks 자체를 별도 객체로 만드는 건 어떨까요?
- 객체 역할과 책임에서 상위 객체에 있는 코드를 하위 객체에게 좀 더 위임해도 좋을 것 같습니다.

CardDeck과 GameTable을 표현하는 뷰 만들기

  • CardDeck 인스턴스를 만들고 랜덤으로 카드를 섞고 출력 화면처럼 보이도록 개선한다.
  • 화면 위쪽에 빈 공간을 표시하는 UIView를 4개 추가하고, 우측 상단에 UIImageView를 추가한다.
    • 상단 화면 요소의 y 좌표는 20pt를 기준으로 한다.
    • 7장의 카드 이미지 y 좌표는 100pt를 기준으로 한다.
  • 앱에서 Shake 이벤트를 발생하면 랜덤 카드를 다시 섞고 다시 그리도록 구현한다.

싱글탭이벤트 처리 flow변경

  • 현재는 DeckView내부에서 모두처리하므로 view내에 로직도 들어있는 상태임
  • 더블탭 처리flow처럼
    • DeckView에서 이벤트 받음 - CardGameDelegate에게 Notify - Deck담당 Model에게 로직실행 - 로직 실행으로 인해 변경된 상태 VC에 Notify - DeckView에게 redraw호출 로 변경할 것

Card 객체에따라 카드 이미지 만들기

<관련 요구사항>

  • Card 객체에 파일명을 매치해서 해당 카드 이미지를 return 하는 메소드를 추가한다.
  • Card 객체가 앞면, 뒷면을 처리할 수 있도록 개선한다.

우측 상단에 Stack에 표시한 카드 제외한 나머지를 CardDeck으로표시

  • 카드스택에 표시한 카드를 제외하고 남은 카드를 우측 상단에 뒤집힌 상태로 쌓아놓는다.
  • 맨위에 있는 카드를 터치하면 좌측에 카드 앞면을 표시하고, 다음 카드 뒷면을 표시한다.
  • 만약 남은 카드가 없는 경우는 우측에도 빈 카드를 대신해서 반복할 수 있다는 이미지(refresh)를 표시한다.

CardGameDelegate클래스를 싱글톤으로 구현

  • Deck, Foundation, StacksView, OneStack 클래스에서 모두 CardGameDelegate(CardGameManageable)가 필요함.
  • 현재 상위모듈에서 하위모듈을 초기화할때 매번 객체를 넘겨주는 방식인데, 한 게임 당 하나의 CardGameDelegate가 필요하고 모든 뷰는 그 객체를 공유하면서 프로그램을 진행해야함
  • 매번 서브뷰를 초기화할때마다 객체를 넘기는 것이 아니라 싱글톤으로 만드는 것이 더 효율적임

currentFrames값을 RootView기준으로 converting하기

  • 드래그 액션이 다 끝난 지점을 뜻하는 currentFrame
  • currentFrame은 movableViews의 원래 superView기준으로 나온다 (OneStack or FoundationView)
  • RootView의 origin기준으로 변환하여 isInside(point:)를 파악한다.

CardImage의 Frame에서 origin을 계산하는 객체 수정- 카드 크기만 고정

StackView들을 7개로 나누면서 Deck, Foundation, StacksView에서 카드 Frame의 origin을 계산하는 방법이 달라짐. (특히 x좌표)
특히 x좌표는 PositionX라는 enum으로 저장되어있고 모든 카드이미지의 크기또한 고정값이라 매번 CardMaker클래스에서 계산할 필요성이 없어보임.

  • 카드크기또한 PositionX, PositionY처럼 고정된 값으로 놔두고 달라진 origin값만 각 뷰에서 부여하는 방식으로 CardFrame을 초기화하는 방법이 좋을 것 같다!

드래그 앤 드롭 - UIPanGestureRecognizer의 .ended구현

(이미 사용자가 뷰 내에서 원하는 곳까지 드래그를 마친 상태. 뷰만 옮겨져있음)

.ended 일때의 로직 flow

  • 카드위치 확인 - movableViews의 마지막 위치가 toFrame위치 내부에 진입했는지확인
    • (toFrame 값의 origin에서 오른쪽/하향 방향으로 카드 가로세로 위치 내부인지 확인)
  • 모델에서 확인 - 해당 toFrame에 현재 movable cards들이 위치할 수 있는지
  • 애니메이션적용 - 모델에서 확인한 값이 true이면 사용자가 드래그를 놓은 위치에서 카드스택의 맞는 위치까지 animation적용

subclass CardImageView의 사이즈는 속성으로 고정하고 VC에서 origin만 부여하여 생성하는 방법

Q. 어차피 카드 이미지의 크기는 모두 똑같고, 계산해놓은 카드 이미지 사이즈을 알 수 있기 때문에 CardImageView에 속성을 지정해놓은것처럼 사이즈도 fix시키고, VC에서는 origin만 알려주고 생성하면 더 효율적이지 않을까?

<예상되는 문제점>

  • 지금은 cardSize라는 computed property가 VC에 있기때문에, VC의 루트 뷰 frame 기준으로 카드의 가로, 세로 길이를 구한 것을 코드만 봐도 알 수 있다. 하지만 만약 이 값을 CardImageView내부에서 바로 상수로쓰면, 다른 사람은 이 값이 어떻게 구해진건지 알 수 없다.
  • 또한 루트뷰에 추가되는 서브뷰이므로 사이즈를 계산하고 부여하는게 VC에서 바로 볼 수 있는게 더 자연스러울 수도 있다.
  • 또한 만약 디바이스가 변경된다면 루트뷰의 frame이 달라지기때문에 상수로 선언한 카드사이즈가 다른 디바이스 뷰 상에서는 맞지 않을 수 있다.
  • 매번 subView의 크기를 루트뷰의 frame에 따라 계산해야한다면, subView에서는 superView의 frame을 알아와서 계산하고, 계산한 값을 init()내부에서 size로 fix할 수 있는지? 만약 가능하다면 이 방법이 자연스러운건지?_?

deckView에서 카드를 드래그했을때 toInfo()리턴값이 nil인 문제

  • 문제상황: deckView에서 카드를 드래그했을때 앱이 종료됨
  • 원인:
    • 드래그가 끝난 지점인 currentFrame을 rootView기준으로 변환하면 nil이 리턴됨
    • nil로 값이 할당된 toInfo: MoveInfo로 rule을 체크하려고 하면 에러발생
  • 해결: rootView기준으로 origin을 변환할때 deckView에서 움직인 방향을 고려해서 변환하기

더블탭과 카드게임 룰 적용

  • 만약 A카드인 경우 왼쪽 상단의 비어있는 칸으로 UIImageView를 animate 시킨다.
    • 기존에 다른 A카드가 있으면 그 옆으로 이동한다.
  • K카드인 경우 스택 중에서 좌측부터 빈 칸이 있으면 빈 칸으로 이동시킨다.
  • 나머지 2이상 카드인 경우 왼쪽 상단에 같은 모양의 A가 있는 경우는 그 위로 이동시킨다.
  • 상단으로 이동할 수 없는 경우, 스택 중에서 좌측부터 앞면으로 된 카드 중 가장 위에 있는 카드와 다음 조건을 확인하고 조건에 맞으면 그 위로 이동시킨다.
    • 숫자가 하나 큰 카드가 있는지 확인한다. ex) 터치한 카드가 2인 경우 3, 10인 경우 J
    • 모양의 색이 다른지 확인한다. ex) 터치한 카드가 ♠️♣️ 이면 ♥️♦️
  • 스택에서 마지막 카드가 다른 곳으로 이동한 경우 다시 마지막 카드를 앞면으로 뒤집는다

animation complete후 모델이 제대로 업데이트되지 않는 문제

image

  • 그림에서 보면 맞는 위치에 드래그 후 게임 룰에 맞게 뷰가 옮겨진 것을 알 수 있음 ( A♥️, 9♦️)
  • 하지만 원래 위치에있던 카드모델이 제거되지 않았기때문에 뷰를 reload하면서 원래 위치에도 또 뷰가 그려짐
  • 바뀐 위치에 있는 카드뷰는 movableView로 removeFromSuperView되어야 하는 대상임.
  • 모델 업데이트하는 부분 수정하여 뷰와 싱크 맞추기
  • CardGameManager.ruleCheck()

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.