Skip to content
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
93 changes: 23 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,30 @@
# 숫자 야구 게임 과제 (Week 2)
숫자 야구 게임은 1에서 9까지의 서로 다른 3개의 숫자를 맞추는 게임입니다. <br>
각 레벨마다 기능이 확장되며, 팀원들이 협업하여 매주 주어진 과제를 해결해 나갑니다.
# ⚾️ 숫자 야구 게임

# 📝 협업 규칙
---
### 🏟️ 개요
숫자 야구 게임은 랜덤으로 생성된 서로 다른 숫자를 맞추는 게임입니다. <br>
각 레벨마다 확장된 기능 업데이트가 요구되었습니다.

### 풀 리퀘스트 작성 규칙
**1** 형식: `[레벨] 작업 내용 - 팀원 이름`
- 예시: `[Lv_1] 야구 로직 구현 - 김상민`<br>
**2** 작업 세부 사항: 해당 레벨에서 구현한 주요 기능을 요약하여 추가 설명으로 작성합니다.
|개발 기간|인원|개발 언어|개발 환경|
|------|--|------|------|
|24.11.05 - 07|1인|Swift|OS: macOS 15.0.1, IDE: Xcode 16.1|

### 레포지토리 설정 및 브랜치 관리
**1** **Fork로 가져오기**: 각 팀원은 레포지토리를 Fork하여 자신의 개인 레포지토리로 가져옵니다.<br>
**2** **브랜치 생성**: Fork한 개인 레포지토리에서 각자의 이름을 딴 브랜치를 생성합니다.<br>
**3** **Pull Request**: 과제를 마친 후, 각자의 브랜치로 Pull Request를 생성하여 코드 리뷰를 요청합니다. 모든 팀원이 Pull Request에 코멘트를 달고 피드백을 제공합니다. <br>
**4** **수정 및 Merge**: 피드백을 반영하여 수정하고, 팀원들의 동의를 얻은 후 merge를 진행합니다. <br>
---

이 과정을 통해 서로의 코드에 대해 이해를 높이고, “왜 이렇게 작성했는지”에 대한 질문과 답변을 주고받으며 과제를 진행합니다.
# 📂 코드 파일 구조
* main.swift: 게임의 메인 진입점으로, 레벨을 선택하고 시작할 수 있도록 구성되어 있습니다. startGame() 함수가 게임의 시작을 담당합니다.
* Lv_1.swift ~ Lv_6.swift: 각 레벨별 요구사항에 맞게 구현된 파일입니다. 각 파일에는 해당 레벨의 기능을 구현하는 함수가 포함되어 있습니다.
### 📂 코드 파일 구조
* main.swift: 인스턴스들이 생성되고 호출되는 곳입니다.
* BaseballGame : 게임이 진행되는 곳입니다.
* Model : 게임 진행을 위해 필요한 데이터 클래스 혹은 구조체 등이 모여 있는 곳입니다.
* PrintManager : 게임 메시지 출력을 도와주는 구조체입니다.
* RecordManager : 게임 기록을 도와주는 구조체입니다.

⠀📜 구현 가이드
---

### main.swift
Command Line Tool 프로젝트에서는 하나의 `main.swift` 파일에서만 프로그램을 시작할 수 있습니다. 따라서 각 레벨별 기능을 별도의 파일로 구현한 후, `main.swift` 파일에서 해당 레벨의 함수를 호출하여 실행하도록 구성합니다.
### 💬 PR
* [게임 시작 구현](https://github.com/SpartaCoding-iOS5/Week2-BaseballGame/pull/7)
* [게임 구현 마무리](https://github.com/SpartaCoding-iOS5/Week2-BaseballGame/pull/21)

---

```swift
import Foundation

func startGame() {
print("레벨을 선택하세요 (1, 2, 3, 4, 5, 6):")

if let input = readLine(), let level = Int(input) {
switch level {
case 1:
levelOne()
case 2:
levelTwo()
case 3:
levelThree()
case 4:
levelFour()
case 5:
levelFive()
case 6:
levelSix()
default:
print("유효하지 않은 레벨입니다. 1~6까지를 선택해주세요.")
}
} else {
print("잘못된 입력입니다.")
}
}

// 게임 시작
startGame()
```
* startGame() 함수에서 게임의 레벨을 선택하여 시작할 수 있도록 구성합니다.
* 사용자가 입력한 레벨 번호에 따라 해당 레벨의 함수를 호출하여 게임을 진행합니다.


### 각 레벨 파일 (Lv_1.swift ~ Lv_6.swift) - 구현 파일

**Lv_1.swift**

```swift
import Foundation

func levelOne() {
// 1. 정답을 생성하는 로직을 추가합니다.
// 2. 유저가 정답을 맞출 때까지 반복해서 입력을 받습니다.
// 3. 입력값이 유효한지 검사하고 힌트를 제공하는 기능을 구현합니다.
}
```

각 레벨별로 구현하시면 됩니다.
### 🐞 트러블 슈팅
* [문자열 enum 내 상수값으로 저장하기](https://chhue96.tistory.com/124)
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
MACOSX_DEPLOYMENT_TARGET = 15.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
Expand All @@ -258,6 +259,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
MACOSX_DEPLOYMENT_TARGET = 15.0;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "3C6C94EE-9FB9-455B-9754-A7CEB2007EA8"
type = "1"
version = "2.0">
</Bucket>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>Week2-BaseballGame.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>
186 changes: 186 additions & 0 deletions Week2-BaseballGame/Week2-BaseballGame/BaseballGame.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//
// baseballGame.swift
// Week2-BaseballGame
//
// Created by CHYUN on 11/5/24.
//


class BaseballGame {

// 기록 매니저
private var recordManager: RecordManager
private var printManager: PrintManager
init(recordManager: RecordManager, printManager: PrintManager) {
Copy link
Contributor

Choose a reason for hiding this comment

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

class 안에서 recordManager = RecordManager()와 같은 방식으로 작성했었어도 되었을 것 같은데, 외부에서 주입받으시는 이유가 궁금합니다~

self.recordManager = recordManager
self.printManager = printManager
}


// 게임 상태
private var gameStatus: GameStatus = .on {
willSet(newStatus) {
Copy link
Contributor

Choose a reason for hiding this comment

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

willset을 활용해 case마다 print가 실행되게 해 주셨군요!
제 개인적인 생각으로는 변수에 값을 할당했을 때 print가 수행되는 구조는 직관적이지 못할 수 있을 것 같아요.
만약 gameStatus 변수가 다른 파일로 분리되어 있고, 코드를 처음 보는 누군가가 startGame 메소드를 읽고 있다면, gameStatus = .on 코드에서 무엇가 출력되겠다는 예상을 하기 어려울 것 같습니다!

switch newStatus {
case .on : printManager.printGuideance(guide: Guidance.welcome)
case .off : printManager.printGuideance(guide: Guidance.gameEnd)
}
}
}

// 게임 시작
func startGame() {

gameStatus = .on
commandLoop : while true {
let command = readLine()!.replacingOccurrences(of: " ", with: "")

switch command {
case "1":
playGame()
break commandLoop
case "2":
recordManager.showRecord()
printManager.printGuideance(guide: Guidance.inputAgain)

case "3":
gameStatus = .off
break commandLoop
Copy link
Contributor

Choose a reason for hiding this comment

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

switch case에서 반복문을 break하기 위한 문법 사용 좋습니다~

default :
printManager.printError(error: InputError.invalidInputError)
printManager.printGuideance(guide: Guidance.inputAgain)
}
}
}


func playGame() {

let answer = newGame() // 랜덤 값 받아오기
var score = Score()
var tryCount = 0


while gameStatus == .on {

// 플레이어에게 값 받기
let playerInput = getPlayerInput()

do {
// 예외 없을 경우
let playerInputAsArray = try checkError(input: playerInput)

score = calculateScore(playerInput: playerInputAsArray, answer: answer)

if score.strikeCount == GameConfig.numberOfAnswer {
printManager.printGuideance(guide: Guidance.hit)
recordManager.recordSet(tries: tryCount + 1)
startGame()
} else if score.outCount == GameConfig.numberOfAnswer {
printManager.printGuideance(guide: Guidance.out)
} else {
print("\(score.strikeCount) \(Guidance.strike) \(score.ballCount) \(Guidance.ball)")
}

score = Score()
tryCount += 1

// 예외 경우
} catch InputError.inputCountError {
Copy link
Contributor

Choose a reason for hiding this comment

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

각각의 error를 catch해서 printManager에 전달하는 방법 보다 좋은 방법이 있을 수도 있을 것 같아요!
만약 에러가 늘어나거나 줄어드는 등 변경된다면 catch문도 계속해서 변경되어야 하는 문제가 예상됩니다..!

printManager.printError(error: InputError.inputCountError)

} catch InputError.invalidInputError {
printManager.printError(error: InputError.invalidInputError)

} catch InputError.zeroInputError {
printManager.printError(error: InputError.zeroInputError)

} catch InputError.duplicateValueError {
printManager.printError(error: InputError.duplicateValueError)
} catch {
printManager.printError(error: InputError.unknownError)
}
}

}


private func calculateScore(playerInput: [Int], answer: [Int]) -> Score {
var score = Score()
for (index, number) in playerInput.enumerated() {
if answer [index] == number {
score.strikeCount += 1
} else if answer.contains(number) {
score.ballCount += 1
} else {
score.outCount += 1
}
}
return score
}


// 랜덤 값 추출
private func newGame() -> [Int] {
Copy link
Contributor

Choose a reason for hiding this comment

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

랜덤 값을 추출하는 기능만을 가진다면, 함수명에 해당 기능을 더 잘 나타내는 이름을 붙이면 좋을 것 같아요!

var answer: [Int] = []
var questionBoxes = ""
var range = GameConfig.rangeOfAnswerWithoutZero

while answer.count < GameConfig.numberOfAnswer {

let randomNumber = Int.random(in: range)
if !answer.contains(randomNumber) {
answer.append(randomNumber)
range = GameConfig.rangeOfAnswerWithZero // 랜덤 범위 - 0을 포함한 범위로 변경
questionBoxes.append("[ ? ] ")
}
}

printManager.printGuideance(guide: Guidance.gameStart)
print(questionBoxes)

print(answer) //test
return answer
}

// 입력 값 받아와서 필요한 처리 후 리턴
private func getPlayerInput() -> String {
printManager.printGuideance(guide: Guidance.requireInput)
return readLine()?.trimmingCharacters(in: .whitespaces) ?? ""
}


// 예외 상황 체크
private func checkError(input: String) throws -> [Int] {
Copy link
Contributor

Choose a reason for hiding this comment

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

이것도 입력값에 대한 예외처리 체크라는 의도를 더 잘 나타낼 수 있는 이름이면 좋지 않을까 생각해 봅니다!


// 한 자씩 파악하기 위해 배열로 쪼개기
let inputAsStringArray = input.split(separator: "")

// 1. 입력 값 개수가 요구치와 다른지 체크
guard inputAsStringArray.count == GameConfig.numberOfAnswer else {
Copy link
Contributor

Choose a reason for hiding this comment

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

이미 알고 계시겠지만 String 값 자체에서도 count를 얻어올 수 있는 점 공유드립니다~!

throw InputError.inputCountError
}

// String 배열 -> Int 배열
let inputAsIntArray = inputAsStringArray.map({ Int($0) })

// 2. String값 Int로 바꿀경우 nil, 숫자 아닌 값 입력되었는지 체크
guard !inputAsIntArray.contains(nil) else {
throw InputError.invalidInputError
}

// 3. 0이 첫 자리에 입력되었는지 체크
guard inputAsIntArray.first != 0 else {
throw InputError.zeroInputError
}

// 4. 중복 값 입력되었는지 체크
guard Set(inputAsIntArray).count == inputAsIntArray.count else {
throw InputError.duplicateValueError
}

// 모든 예외 상황이 아닐 경우 값비 교를 위한 배열 리턴, 위에서 nil 체크 완료
return inputAsIntArray.map({ $0! })

}

Copy link
Contributor

Choose a reason for hiding this comment

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

채현님 고수시니까 요 부분도 살짝 말씀드릴게요!

struct SomeStruct {
  func someFunction() {

  }
  <-- 요기 빈 줄이 있을 때도 있고 없을 때도 있는데요, 이러한 컨벤션도 일관적으로 지켜주시면 더욱 깔끔한 코드가 될 것 같아요!
}

}

This file was deleted.

21 changes: 0 additions & 21 deletions Week2-BaseballGame/Week2-BaseballGame/Lv_2(필수구현).swift

This file was deleted.

15 changes: 0 additions & 15 deletions Week2-BaseballGame/Week2-BaseballGame/Lv_3(도전구현).swift

This file was deleted.

24 changes: 0 additions & 24 deletions Week2-BaseballGame/Week2-BaseballGame/Lv_4(도전구현).swift

This file was deleted.

Loading