Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

묵찌빠 게임 [STEP 2] Ari, 알라딘 #97

Closed
wants to merge 32 commits into from

Conversation

leeari95
Copy link
Member

@leeari95 @junbangg
@soll4u

안녕하세요. Soll !!

묵찌빠 게임 STEP 2 PR을 보냅니다.

혹시 저희가 고민한 것 외에 놓친 부분이 있다면 번거롭더라도 한 번만 더 짚어주신다면 감사드리겠습니다.

이번 피드백도 잘 부탁드립니다...!!! 🥰

고민했던 것

  • 재귀를 활용하여 구현하는 방법과, 반복문을 활용하여 구현하는 방법에 대해서 고민했습니다.

    • 재귀
      • 장점:
        • 코드가 훨씬 간결하다.
      • 단점:
        • 디버깅이 어렵다.
        • 반복문보다 성능이(메모리, 속도)가 떨어질 수 있다.
    • 반복문
      • 장점
        • 재귀보다 디버깅이 수월
      • 단점
        • 코드의 양이 많다
        • 가독성이 저하됨
    • 이런 장단점들을 고려했을때, 어떤 방법을 선택하는지에 대한 고민을 했는데 아직도 잘 모르겠습니다.
  • 가위바위보에서 묵찌빠로 넘어가는 부분을 Bool?로 반환하여 활용해보았습니다.

  • 가위바위보에서 사용했던 승패판정 기능을 묵찌빠에서 활용할 수 있는 방법을 고민하였습니다.

  • 가위바위보에서의 패가 같을 때, 묵찌빠에서의 패가 같을 때의 게임이 돌아가는 로직이 달라서 동일한 메서드(isRestartable)로 게임에 따라 다르게 판정할 수 있는 방법을 고민하였습니다.

  • if문, guard문, switch문을 다양하게 활용해볼 수 있는 방법을 고려해보았습니다.

궁금했던 것 / 조언을 얻고 싶은 부분

  • 재귀함수와 반복문으로 둘다 구현해보았는데, 어떤 방법이 더 적절한지 Soll의 의견이 궁금합니다.

    • 재귀함수의 경우 코드는 깔끔해지지만 저희가 경험해본 결과 디버깅이 매우 어렵고, 재귀적으로 호출되는 함수들이 게임이 끝나지 않는 이상 계속 스택이 쌓이는 현상을 겪었습니다. 저희가 고민해보았을 때 성능적으로는 반복문이 더 좋은 것 같다는 판단을 내리고 코드가 좀 지저분해지는 부분을 감수하고 반복문으로 재구현 해보았습니다. 일단 저희가 최대한 깔끔하게 할 수 있는 부분은 정리를 해서 리팩토링을 해보았는데, 놓친 부분은 없는지도 궁금합니다.
    • 재귀함수에서 반복문으로 재구현한 부분의 커밋 해시코드: 1415ae0dd4435ca1dd58f2136577e6be28808a46
  • 사용자/컴퓨터와 관련된 데이터들을 구조체로 묶어서 사용하면 좋을것 같다는 생각을 했는데, 어떤 방법으로 활용해야되는지 떠오르지가 않았습니다.

    struct Player {
        var name: String
        var isTurn: Bool
        
        func getName() -> String {}
        mutating func changeTurn() {}
    }
    • 예를 들어 위와 같이 Player 라는 구조체를 만들어서, 누구의 턴인지를 저장하면서 코드에 활용하는게 좋을것 같았는데 그 이상이 생각이 잘 안납니다. 혹시 이런 방법으로 하기위한 팁이 있으신지 궁금합니다.
    private var isPlayerTurn: Bool? = ScissorsRockPaperGame().isPlayersTurn()
    private var currentTurnHolder: String {
        if isPlayerTurn == true {
             return "사용자"
        }
          return "컴퓨터"
    }
    • 위의 코드를 Player 구조체로 간소화 시킬 수 있지 않을까라는 생각이 있었는데, 떠오르지가 않습니다.
  • 지금 코드에서는 누구의 차례인지를 나타내기위해 isPlayerTurn 이라는 Bool 타입을 사용했습니다. 그래서 true 일때는 사용자의 턴이고, false 일때는 컴퓨터의 턴을 나타낼 수 있게 구현했습니다.

    • Bool 말고, 열거형을 만들어서 case: playersTurn, case: computersTurn 으로 턴을 나타내는 생각도 해봤는데, 혹시 두 방법중에 뭐가 더 괜찮아보이는지 의견이 궁급합니다.
      • 이름이 Bool 타입을 고려하여 지어줬기 때문에 Bool로도 충분히 코드의 가독성을 해치지 않는다 라는 생각이 들었습니다. 열거형으로 만들어주어도 가독성이 올라갈 것 같지만 가독성 때문에 사용자 정의 타입을 구현하는 것이 적절한 방법일까요? 확장성이 향상되는 일이 아닌 이상 그냥 이대로 두어도 괜찮다는 결론이 나왔습니다. 이 부분에 대한 Soll의 의견이 궁금합니다!
  • 지금 repeat-while문 내부가 복잡해보입니다. 저걸 적절하게 정리할 수 있는 방법을 고민해보았지만 해결하지 못했어요. Soll의 조언이 절실히 필요합니다.

leeari95 and others added 17 commits October 14, 2021 15:37
- userTurn 속성 추가
- firstTurn 속성 추가
- firstTurn 프로퍼티 if else 간소화
- 메소드 순서 변경
- 게임모드를 판별하기 위해 enum 'GameMode' 추가
- GameJudgment 내부에 'isRestartable' 내부 로직 변경
- 기존 가위바위보게임 내부에 있던 'isWrongInput' 메서드 GameJudment 내부로 이동
- RockScissorsPaper 구조체 내부에 'startGame' 내부를 재귀함수에서 반복문으로 새롭게 작성함
- changeTurn 메서드 새롭게 생성
- GameManager의 printGameResult 반환 타입 삭제
- ScissorsRockPaperGame의 isPlayersTurn 리턴 로직 변경
Copy link

@soll4u soll4u left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요! 스텝2 수고많으셨습니다 ㅎㅎ

최대한 꼼꼼히 보려 노력했습니다 ㅎㅎ 저번에도 말씀드렸지만 제 말이 정답은 아닙니다!

항상 팀원끼리 상의하시고 논리를 갖고 결정하셨으면 좋겠습니다~

칭찬할 점

  • 재귀 함수와 반복문을 둘 다 사용해보시고 팀만의 논리로 선택해서 구현하신 점 너무 좋습니다!
    두 방법 장단점이 명확한데, 이를 공부하고 정리하신 점 좋습니다 💯
  • 열거형을 적극적으로 쓰셔서 좋습니다! 케이스를 구분하는 데에 열거형은 많이 쓰이니 지금부터 써보시면 좋을 것 같아요 💯
  • 전역 함수를 안 만들고 타입을 선언해서 타입끼리 관계를 엮어준 점 정말 좋습니다! 💯

고민했던 것에 대한 의견

  • 가위바위보에서 묵찌빠로 넘어가는 부분을 Bool?로 반환하여 활용해보았습니다.
    ⇒ 현재 이부분을 isPlayerTurn 프로퍼티로 하셨던데 말씀하신 의도라면 다른 네이밍이었으면 좋을 것 같아요 😄

궁금했던 것 / 조언을 얻고 싶은 부분에 대한 의견

  • 재귀함수와 반복문으로 둘다 구현해보았는데, 어떤 방법이 더 적절한지 Soll의 의견이 궁금합니다.
    ⇒ 저도 두분의 의견과 동일합니다 👍 재귀함수는 말씀하신 대로 스택 오버플로우가 발생할 수 있기 때문에 저는 잘 사용하지 않고 있어요 🙂

  • 사용자/컴퓨터와 관련된 데이터들을 구조체로 묶어서 사용하면 좋을것 같다는 생각을 했는데, 어떤 방법으로 활용해야되는지 떠오르지가 않았습니다.
    ⇒ 공통된 특성을 묶어서 추상화하려는 노력을 하신점 너무 좋습니다 💯 칭찬 백만개
    저라면 일단 사용자와 유저의 특성을 전부 쓸 것 같아요.
    어떤 특성과 행동을 하는지 정의해서 공통되는 부분으로 만드는 것이 추상화일텐데,
    지금 떠오르는 공통된 특성은 hand(낼 수 있는 패) 밖에 생각이 안나네요 😅

    누구의 턴인지를 갖고 있는 게 Player 일까요? 🤔
    그러면 턴이 넘어갈때 마다 사용자와 컴퓨터의 isTurn을 true/false를 toggle 해줘야 하지 않을까요..

    현실세계에서 묵찌빠 게임을 한다면 두명이서 진행하기 때문에 각자가 isTurn을 갖고 있다고 볼수도 있겠죠.
    그렇지만 프로그램 내에서는 프로그램을 진행해주는 사람이 그 역할을 맞게 되지 않을까요?
    설계하기 나름인것 같아요 ㅎㅎ

  • 지금 코드에서는 누구의 차례인지를 나타내기위해 isPlayerTurn 이라는 Bool 타입을 사용했습니다. 그래서 true 일때는 사용자의 턴이고, false 일때는 컴퓨터의 턴을 나타낼 수 있게 구현했습니다.
    ⇒ 113 라인 코멘트로 남겨드렸어요!

  • 지금 repeat-while문 내부가 복잡해보입니다.
    isContinued 의 역할이 정말 메서드를 제어하기 위한 코드인 것 같아요.
    리팩토링을 하게 되신다면 한번에 다 바꾸지 말고 조금씩 분리해보시면 수월하실 것 같아요 ☺️

추가적으로 보완하면 좋을 부분

  • 34 라인에서 return PlayerOption.allCases[Int.random(in: 1...3)]
    ⇒ 낼 수 있는 패가 늘어나면 3을 4, 5 이런식으로 하드코딩 해야할 것 같아요.
    CaseIterable 을 이용해서 어떻게 표현할 수 있을까요?
    그리고 return도 생략 가능해보입니다!

Comment on lines +43 to +46
case (.scissorsRockPaper, true):
print(Message.gameDraw)
return true
case (.rockScissorsPaper, false):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

. scissorsRockPaper. rockScissorsPaper 가 뭔지 보면서 고민했던 것 같아요 ㅎㅎ
변수명에 대해 어떤 고민을 하셨는지 궁금합니다

Copy link
Member Author

@leeari95 leeari95 Oct 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가위바위보와 묵찌빠 둘다 패가 같을때 어떤 액션을 취해야하더라구요.
그래서 가위바위보 모드일땐 true로 반환해서 반복문이 계속 돌아가게 만들고
묵찌빠일땐 false를 반환해서 반복문을 멈추게 하려고 구현해보았어요.
말그대로 게임모드에 따라 반환을 다르게 주려고 고민을 했었고, 가위바위보와 묵찌빠를 어떻게 구별을 할까 고민하다가
현재 게임모드의 케이스 이름들 보다 더 좋은 아이디어가 떠오르지가 않았던 것 같습니다 😅

그렇다고 가위바위보, 묵찌빠에 따라서 다른 메서드로 구현해주자니.. 서로 조건문이 겹쳐서 하나로 그냥 통일시켜서 모드에 따라 다른 값을 반환해주면 어떨까...? 라는 생각을 했었던 것 같습니다.

var playerHand: PlayerOption?
var computerHand: PlayerOption

repeat {
computerHand = PlayerOption.randomHand
print(Message.start, terminator: "")
playerHand = recieveUserInput()
} while gameJudgment.isDraw(playerHand, computerHand) || isWrongInput(playerHand: playerHand)
} while gameManager.isRestartable(mode: .scissorsRockPaper,playerHand, computerHand) || gameManager.isWrongInput(playerHand: playerHand)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(mode: .scissorsRockPaper,playerHand 부분에서 프로퍼티 인자 사이에 띄어쓰기 규칙이 지켜지면 좋을 것 같아요 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗.. 띄어쓰기를 빼먹었네요. 캐치해주셔서 감사합니다!

}

mutating private func changeTurn(when isContinued: Bool, _ playerHand: PlayerOption?, _ computerHand: PlayerOption) {
guard isContinued else { return }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

122~124 번째 라인의 guard문과 컨벤션을 맞추면 좋을 것 같습니다 👍

Comment on lines 139 to 142
guard playerHand != .quit else {
print(Message.gameEnd)
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

135번째 라인에서 return 하지 않은 이유는 띄어쓰기 규칙때문일까요? ㅎㅎ
이유가 궁급합니다 😄

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드에서 빠져나오기 전에 "게임이 종료되었습니다" 메세지를 출력해야되서 135줄에서 return을 안했습니다!


var playerHand: PlayerOption?
var computerHand: PlayerOption
var isContinued = false
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changeTurn(when:_:_:) 메서드를 제어하는 플래그인가요?
역할을 나타낼 수 있는 적합한 네이밍을 고민해보시면 좋을 것 같습니다 😃

}
return false
}

func isPlayerWin(_ playerHand: PlayerOption?, _ opponentHand: PlayerOption) -> Bool {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가위바위보 게임에서만 판단하는 메서드가 GameManager에 있는 이유를 알 수 있을까요? ㅎㅎ
GameManager의 역할은 어디부터 어디까지 일까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아닙니다! 가위바위보 뿐만 아니라 묵찌빠에서도 사용되고 있는 메서드입니다.
묵찌빠에서 서로 패가 같지않을때 턴을 넘겨받는 changeTurn 메서드에서 사용하고 있습니다! ☺️

struct RockScissorsPaperGame {
private let gameManager = GameManager()
private var isPlayerTurn: Bool? = ScissorsRockPaperGame().isPlayersTurn()
private var currentTurnHolder: String {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

연산 프로퍼티를 활용하셨네요! 좋습니다 👍

guard isContinued else { return }
if playerHand == nil {
isPlayerTurn = false
} else if isContinued {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

148 라인에서 guard로 걸러줘서 else if isContinued 가 항상 true일 것 같은데
명시하신 이유가 있을까요? :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

148번 라인이 없다면 첫 게임 시작playerHandnil이기 때문에
무조건 첫시작부터 컴퓨터의 턴으로 넘어가는 상황이 발생합니다.

그래서 첫번째 게임 이후 두번째 게임부터 isContinuedtrue일 때만 changeTurn이 발동하도록 조건을 걸어주었습니다.


mutating private func changeTurn(when isContinued: Bool, _ playerHand: PlayerOption?, _ computerHand: PlayerOption) {
guard isContinued else { return }
if playerHand == nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gameManager.isWrongInput(playerHand: playerHand) 와 같은 역할을 하는 함수인것 같은데
따로 명시하신 이유가 있을까요? 🤔

func isDraw(_ playerHand: PlayerOption?, _ opponentHand: PlayerOption) -> Bool {
if playerHand == opponentHand {
struct GameManager {
func isRestartable(mode: GameMode, _ playerHand: PlayerOption?, _ opponentHand: PlayerOption) -> Bool {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

하나의 메서드로 재사용하려는 노력을 하셨군요! 시도하신 점을 칭찬드리고 싶네요 💯

하지만 처음 메서드를 봤을 때 이해하기 쉽지 않았습니다 😢
하고 있는 역할이 너무 많다고 생각했어요.
실패를 비교하는 기능, 함수를 언제시작할지 판단하는 기능, 가위바위보게임일때 draw를 출력하는 기능,
이런 기능들이 많은 상태에서 모드까지 판단하다보니 가독성이 떨어지는 것 같다고 생각해요 !

어떻게 코드를 개선할 수 있을까요? 😄

@yagom yagom closed this Oct 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants