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] 햄찌, 가마 #230

Open
wants to merge 12 commits into
base: ic_11_hamzzi
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 80 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,83 @@
## iOS 커리어 스타터 캠프
# 묵찌빠 프로젝트

### 묵찌빠 프로젝트 저장소
## 0. 목차
[1. 소개](#1-소개)
[2. 팀원](#2-팀원)
[3. 타임라인](#3-타임라인)
[4. 실행 화면](#4-실행-화면)
[5. 트러블 슈팅](#5-트러블-슈팅)
[6. 팀 회고](#6-팀-회고)
[7. 참고 자료](#7-참고-자료)

- 이 저장소를 자신의 저장소로 fork하여 프로젝트를 진행합니다
## 1. 소개
Step 1는 가위, 바위, 보 게임입니다. 사용자가 숫자 0, 1, 2, 3 중 하나를 입력하여 게임을 진행합니다. 0은 게임 종료, 1은 가위, 2는 바위, 3은 보를 나타냅니다. 1, 2, 3이 아닌 문자를 입력하면 잘못된 입력으로 처리되어 최초 실행 상태로 돌아갑니다. 또한 사용자의 패와 컴퓨터의 패가 같아 비긴 경우에도 최초 실행 상태로 복귀합니다. 컴퓨터는 1, 2, 3 중 랜덤한 숫자를 생성하여 자신의 패를 결정합니다. 사용자가 이기거나(컴퓨터가 지는), 사용자가 지는(컴퓨터가 이기는) 승패가 나누어지는 경우에 대해서만 Step 2로 넘어갑니다.
Step 2는 묵, 찌, 빠 게임입니다. 사용자 입력에 대한 처리는 Step 1과 거의 같습니다. 1은 묵, 2는 찌, 3은 빠를 나타냅니다. Step 2부터는 턴을 활용하게 됩니다. Step 1에서 마지막으로 이긴 쪽이 처음에 턴을 가지게 됩니다. 사용자가 잘못된 입력을 할 경우에 컴퓨터로 턴이 넘어갑니다. 사용자의 패와 컴퓨터의 패가 동일할 경우, 턴을 쥐고 있는 쪽이 승리하게 됩니다. 패가 다른 경우에는 이긴 쪽이 턴을 쥐게 되고 Step 2의 최초 실행 상태로 복귀하여 게임을 계속 진행하게 됩니다.

## 2. 팀원
| [햄찌](https://github.com/kkomgi) | [가마](https://github.com/forseaest) |
| --- | --- |
| <img src="https://avatars.githubusercontent.com/u/65929788?v=4" width="200"> | <img src="https://avatars.githubusercontent.com/u/96014314?v=4" width="200"> |

## 3. 타임라인
| 날짜 | 내용 |
| --- | --- |
| 24.01.08 | Ground Rules 논의, Step1 코드 작성 |
| 24.01.09 | Step1 코드 마무리, Step1 PR |
| 24.01.10 | Step1 리팩토링, Step1 PR |
| 24.01.11 | Step2 코드 작성 |
| 24.01.12 | Step2 리팩토링, Step2 PR |

## 4. 실행 화면
### 4-1. Step 1
| 잘못된 입력 처리 | 진행 | 게임 종료 |
| --- | --- | --- |
| <img src="Screenshot/rsp_invalidinput.png" width="350"> | <img src="Screenshot/rsp_run.png" width="350"> | <img src="Screenshot/rsp_exit.png" width="350"> |
### 4-2. Step 2
| 잘못된 입력 처리 | 진행 | 게임 종료 |
| --- | --- | --- |
| <img src="Screenshot/mcb_invalidinput.png" width="350"> | <img src="Screenshot/mcb_run_1.png" width="350"><br><img src="Screenshot/mcb_run_2.png" width="350"> | <img src="Screenshot/mcb_exit.png" width="350"> |

## 5. 트러블 슈팅
### 5-1. 사용자 입력시, 문자열 유효성 검사
- 정규식을 이용하여 0에서 3까지의 한 자리 숫자만 받을 수 있도록 하였습니다.
- 또한 SQL Injection 을 예방하는데 도움을 줍니다.
- 사용된 정규식은, `#"^[0-3]$"#` 입니다.
### 5-2. 함수 리팩토링
- 작성한 수도코드를 토대로 작성된 코드에서 함수를 분리하여야 하였습니다. 그러나 그 과정에서 중복 소스가 생성되었습니다. 결국 함수 리팩토링하게 되었습니다.
- 특히 기존 코드에서 새 함수를 생성할 때는 함수의 이름을 직관적으로 만들었습니다. 유지보수에 용이하게 만드는 것이 포인트였기 때문입니다.
- 이렇게 완성된 함수만 호출하여 재사용할 수 있기에 작업시간이 줄어들고 프로그램 유지보수에도 굉장히 좋아질 거라고 예상합니다.
### 5-3. rawValue 사용
- enum의 형태에는 원시값 추가가 가능하여 불필요한 변수 선언을 하지 않게 되었습니다.
```Swift
enum GamePlayer: String {
case player = "사용자"
case computer = "컴퓨터"
...
}
```
- 튜플(Tuple)을 사용하여 하나의 케이스에 서로 다른 연관값들을 저장하였고 아래와 같이 원시값을 불러올 수 있도록 하였습니다.
- enum 에 대해서 새로운 방법을 알게 되는 계기가 되었습니다.
```Swift
switch gameResult {
case .win:
whoseTurn = .player
print("\(whoseTurn.rawValue)의 턴입니다.")
case .lose:
whoseTurn = .computer
print("\(whoseTurn.rawValue)의 턴입니다.")
default:
print("\(whoseTurn.rawValue)의 승리!")
loopFlag = false
}
```

## 6. 팀 회고
| 햄찌 | 가마 |
| --- | --- |
| 제시한 프로젝트를 의도를 이해하고 로직을 순서대로 구현하기 위하여 수도코드부터 작성하였습니다. 그리고 추후 유지보수를 생각하여 메소드 목적에 따라 정규식 규칙을 정할 수 있도록 하였습니다. 팀원과 같이 작업하면서 메소드의 재활용성에 대해서 더 생각할 수 있었습니다. | 사용자로부터 문자열을 입력 받을 때 정규식을 통해 유효성과 특정 필터링을 걸 수 있다는 것을 체득하게 되어, 프로그램뿐만 아니라 코딩 테스트에서도 활용할 수 있을 것 같다고 긍정적으로 생각되는 보람 찬 프로젝트였습니다. |

## 7. 참고 자료
- https://www.swift.org/documentation/api-design-guidelines/
- https://developer.apple.com/documentation/swift
- 클린 코드
- 객체지향의 사실과 오해
135 changes: 116 additions & 19 deletions RockPaperScissors/RockPaperScissors/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,151 @@

import Foundation

let regex = #"^[0-3]"#
func isNumber(_ input: String) -> Bool {
return input.range(of: regex, options: .regularExpression) != nil
enum GamePlayer: String {
case player = "사용자"
case computer = "컴퓨터"
}

enum GameResult {
case draw
case win
case lose
}
Comment on lines +9 to 18

Choose a reason for hiding this comment

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

enum을 활용하신 부분 너무 좋아요👍


func gameRun() {
enum GameState {
case rockScissorPaper
case mukChiba
}
Comment on lines +20 to +23

Choose a reason for hiding this comment

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

해당 타입을 활용하여 어떤 게임을 실행할지 제어하는게 아닌, 단순히 명시적으로 보여주기 위함이라고 한다면 게임을 실행하는 함수명을 runRockScissorsPaper, runMukChiBa 등으로 바꿔주는 것만으로도 충분할 것 같다고 생각이 드네요.


var gameState: GameState = .rockScissorPaper
var whoseTurn: GamePlayer = .computer

Choose a reason for hiding this comment

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

turn의 초기값이 computer인게 어색하다고 볼 수도 있을 것 같아요.
해당 변수를 옵셔널로 두거나 GamePlayer에 누구의 턴도 아닌 case를 추가한다면 좋을 것 같아요🙂


func runGameFirstStep() {
var loopFlag = true
let regex = #"^[0-3]$"#

while loopFlag {
print("가위(1), 바위(2), 보(3)! <종료 : 0> :", terminator: " ")

let userInput = readLine()
let playerInput = readLine()

guard let safeUserInput = userInput else {
guard let playerInputChoice = playerInput else {
print("잘못된 입력입니다. 다시 시도하여 주세요.")
loopFlag = false
return
}

if (!isNumber(safeUserInput)) {
if (!isValidInput(input: playerInputChoice, regex: regex)) {
print("0-3까지의 수를 입력해주세요.")
continue
}

if (safeUserInput == "0") {
if (playerInputChoice == "0") {
print("게임 종료")
return
}

judgeGame(userChoice: safeUserInput)
let computerChoice = makeComputerChoice()
let gameResult = judgeRockScissorPaper(playerChoice: playerInputChoice, computerChoice: computerChoice)

print("컴퓨터: \(computerChoice)")
printResult(gameResult: gameResult)

if gameResult == .draw {
continue
} else {
loopFlag = false
gameState = .mukChiba
whoseTurn = gameResult == .win ? .player : .computer
runGameSecondStep()
}
}
}

func judgeGame(userChoice: String) {
let computerChoice = makeComputerChoice()
func runGameSecondStep() {
var loopFlag = true
let regex = #"^[0-3]$"#

switch (userChoice, computerChoice) {
case (_, _) where userChoice == computerChoice:
print("비겼습니다!")
case ("1", "3"), ("2", "1"), ("3", "2"):
print("이겼습니다!")
default:
print("졌습니다!")
while loopFlag {
print("[\(whoseTurn.rawValue)의 턴] 묵(1), 찌(2), 빠(3)! <종료 : 0> :", terminator: " ")

let playerSecondInput = readLine()

guard let playerSecondChoice = playerSecondInput else {
print("잘못된 입력입니다. 다시 시도하여 주세요.")
loopFlag = false
return
}

if (!isValidInput(input: playerSecondChoice, regex: regex)) {
print("0-3까지의 수를 입력해주세요.")
continue
}

if (playerSecondChoice == "0") {
print("게임 종료")
return
}

let computerSecondChoice = makeComputerChoice()
let gameResult = judgeMukChiBa(playerChoice: playerSecondChoice, computerChoice: computerSecondChoice)

print("컴퓨터: \(computerSecondChoice)")

switch gameResult {
case .win:
whoseTurn = .player
print("\(whoseTurn.rawValue)의 턴입니다.")
case .lose:
whoseTurn = .computer
print("\(whoseTurn.rawValue)의 턴입니다.")
default:
print("\(whoseTurn.rawValue)의 승리!")
Comment on lines +103 to +108

Choose a reason for hiding this comment

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

enum의 rawValue를 활용하신 부분 너무 좋아요👍
추가로 CustomStringConvertible에 대해서도 알아보시면 좋을 것 같아요🙂
꼭 사용해야 한다는 것은 아닙니다!

loopFlag = false
}
}
}

func isValidInput(input: String, regex: String) -> Bool {
return input.range(of: regex, options: .regularExpression) != nil
}

func makeComputerChoice() -> String {
let computerChoice = String(Int.random(in: 1...3))
return String(computerChoice)
}
Comment on lines 118 to 121

Choose a reason for hiding this comment

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

저는 굳이? 라는 생각이 드는 부분이긴 한데
swift api guidelines에 따르면 factory method의 시작은 make로 시작해달라는 내용이 있어서 그 이외에 함수에는 최대한 make단어로 시작하지 않는 것으로 알고 있어요.


gameRun()
func judgeRockScissorPaper(playerChoice: String, computerChoice: String) -> GameResult {
switch (playerChoice, computerChoice) {
case (_, _) where playerChoice == computerChoice:
return .draw
case ("1", "3"), ("2", "1"), ("3", "2") :
return .win
default:
return .lose
}
}

func judgeMukChiBa(playerChoice: String, computerChoice: String) -> GameResult {
switch (playerChoice, computerChoice) {
case (_, _) where playerChoice == computerChoice:
return .draw
case ("1", "3"), ("2", "1"), ("3", "2") :
return .lose
default:
return .win
}
}
Comment on lines +123 to +143

Choose a reason for hiding this comment

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

숫자만으로는 어떤 것을 기준으로 승패가 나뉘는지 명확하게 이해하기가 어려울 수도 있어서 choice또한 enum으로 정의해준다면 더 쉽게 이해할 수 있을꺼 같아요🙂


func printResult(gameResult: GameResult) {

Choose a reason for hiding this comment

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

argument label을 추가해준다면 해당 함수를 사용하는 곳에서 가독성이 더 좋아질 수도 있을 것 같아요🙂

func printResult(of gameResult: GameResult) { }
printResult(of: gameResult)

switch gameResult {
case .draw:
print("비겼습니다!")
case .win:
print("이겼습니다!")
case .lose:
print("졌습니다!")
}
}

runGameFirstStep()

Choose a reason for hiding this comment

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

지금은 함수만으로 기능을 구현했지만 나중에 시간이 되신다면 struct/class나 protocol을 활용하여 가위바위보와 묵찌빠를 두 가지 타입으로 나누어 구현해보시면 좋을 것 같아요🙂

Binary file added Screenshot/mcb_exit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshot/mcb_invalidinput.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshot/mcb_run_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshot/mcb_run_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshot/rsp_exit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshot/rsp_invalidinput.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Screenshot/rsp_run.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.