-
Notifications
You must be signed in to change notification settings - Fork 6
[Lv_1 - Lv_6] 게임 시작 부분, 값 비교 등 구현 - 박채현 #21
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
base: Chae-Hyun
Are you sure you want to change the base?
Changes from all commits
4eb1d43
90ed646
c56fdf5
8257905
1aa0f80
1ef9c4a
e131d66
a46004e
9eb2663
e4f4c17
56f780f
133d4ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
@@ -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> |
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) { | ||
self.recordManager = recordManager | ||
self.printManager = printManager | ||
} | ||
|
||
|
||
// 게임 상태 | ||
private var gameStatus: GameStatus = .on { | ||
willSet(newStatus) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. willset을 활용해 case마다 print가 실행되게 해 주셨군요! |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 각각의 error를 catch해서 printManager에 전달하는 방법 보다 좋은 방법이 있을 수도 있을 것 같아요! |
||
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] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이것도 입력값에 대한 예외처리 체크라는 의도를 더 잘 나타낼 수 있는 이름이면 좋지 않을까 생각해 봅니다! |
||
|
||
// 한 자씩 파악하기 위해 배열로 쪼개기 | ||
let inputAsStringArray = input.split(separator: "") | ||
|
||
// 1. 입력 값 개수가 요구치와 다른지 체크 | ||
guard inputAsStringArray.count == GameConfig.numberOfAnswer else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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! }) | ||
|
||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 채현님 고수시니까 요 부분도 살짝 말씀드릴게요! struct SomeStruct {
func someFunction() {
}
<-- 요기 빈 줄이 있을 때도 있고 없을 때도 있는데요, 이러한 컨벤션도 일관적으로 지켜주시면 더욱 깔끔한 코드가 될 것 같아요!
} |
||
} |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class 안에서 recordManager = RecordManager()와 같은 방식으로 작성했었어도 되었을 것 같은데, 외부에서 주입받으시는 이유가 궁금합니다~