이전 글
기존 코드: 처음 게임 메뉴입력을 받고 input에 따라 바로 switch로 분기처리
피드백: enum을 사용하면 맥락을 드러내고 안전한 코드를 작성할 수 있다.
func start() {
while true {
print("환영합니다! 원하는 번호를 입력해주세요.")
print("1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기")
let insertNum = readLine()
switch insertNum {
case "1":
gameMode() // 게임 실행
recordManager.add(record: record) // 게임이 끝난 후, 총 시도 횟수를 add함수로 전달
case "2":
recordManager.showRecords() // 게임 진행 상황 호출
case "3":
print("< 숫자 야구 게임을 종료합니다. >")
return // 게임 종료 후 함수 종료
default:
print("올바르지 않은 입력입니다. 1, 2, 3 중에서 선택해주세요.")
}
}
}
enum Mode: String {
case gameStart = "1"
case gameRecord = "2"
case gameExit = "3"
}
func start() {
while true {
print("환영합니다! 원하는 번호를 입력해주세요.")
print("1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기")
guard let insertNum = readLine() else {
print("프로그램이 종료되었습니다")
exit(0)
}
switch Mode(rawValue: insertNum) {
case .gameStart:
gameMode() // 게임 실행
recordManager.add(record: record) // 게임이 끝난 후, 총 시도 횟수를 add함수로 전달
case .gameRecord:
print("<게임 기록>")
print(recordManager.showRecords()) // 게임 진행 상황 호출
case .gameExit:
print("< 숫자 야구 게임을 종료합니다. >")
return // 게임 종료 후 함수 종료
default:
print("올바르지 않은 입력입니다. 1, 2, 3 중에서 선택해주세요.")
}
}
}
//정답 생성 함수
func makeAnswer() {
var numArr = [0,1,2,3,4,5,6,7,8,9] // 정답 숫자를 만들 때 쓰이는 배열
randomNums = [] //초기화
for i in 0...2 {
if i == 0 { // 1번째 정답 숫자 생성
// 0을 제외한 숫자에서 선택하도록 설정
let nonZeroNumbers = numArr.filter { $0 != 0 }
if let num = nonZeroNumbers.randomElement() {
randomNums.append(num)
numArr.remove(at: numArr.firstIndex(of: num)!)
}
} else { // 2,3 번째 정답 숫자 생성
if let num = numArr.randomElement() {
randomNums.append(num)
numArr.remove(at: numArr.firstIndex(of: num)!)
}
}
}
}
func makeAnswer() {
answer = [Int.random(in: 1..<10)]
while answer.count < 3 {
let number = Int.random(in: 0..<10)
guard !answer.contains(number) else { continue }
answer.append(number)
}
}
guard input.count == 3, input[0] != 0, Set(input).count == 3 else {
print("올바르지 않은 입력 값 입니다.")
continue
}
// 숫자 입력 및 유효성 검사
if let line = readLine() {
// 입력된 문자열을 숫자 배열로 변환
input = line.split(separator: "").compactMap { Int($0) }
guard input.count == 3 else {
print("올바르지 않은 입력 값 입니다. 3자리 숫자를 입력하세요.")
continue
}
guard input[0] != 0 else {
print("올바르지 않은 입력 값 입니다. 첫번째 숫자는 0이 될 수 없습니다.")
continue
}
guard Set(input).count == 3 else {
print("올바르지 않은 입력 값 입니다. 중복된 숫자는 입력될 수 없습니다.")
continue
}
}
피드백 내용:
BaseballGame클래스가 많은 책임을 가지고 있어요.
말씀해주신 것처럼 힌트 계산뿐만 아니라 정답을 만들고 인풋을 파싱하는 등의 책임을 다른 컴포넌트로 분리 할 수 있습니다.
1. gameMode 함수: 사용자 입력 및 게임 종료 조건 확인
2. makeHintNumber 함수: 스트라이크 및 볼 계산
3. printHint 함수: 게임 결과 출력
// 스트라이크 및 볼 계산
func makeHintNumber() -> (Int, Int) {
var strike = 0
var ball = 0
for i in 0..<input.count {
if input[i] == randomNums[i] { //스트라이크 계산
strike += 1
} else if randomNums.contains(input[i]) { //볼 계산
ball += 1
}
}
return (strike, ball)
}
// 게임 결과 출력
func printHint(strike: Int, ball: Int) -> String {
var hint: String = ""
//힌트 출력
if strike == strikeCount {
hint = "정답입니다!"
} else if strike == minCount && ball == minCount {
hint = "Nothing"
} else {
hint = "\(strike) Strike \(ball) Ball"
}
return hint
}
//gameMode함수 하단부에 makeHintNumber함수, printHint함수 호출
let (strike, ball) = makeHintNumber() //스트라이크, 볼 계산
print(printHint(strike: strike, ball: ball)) //결과 출력
if strike == strikeCount { // 게임 종료 조건
return
}
피드백 내용:
RecordManager도 생각해보면 조금 모호한 면이 있습니다.
add함수는 게임의 기록을 저장하는 데 showRecords는 저장된 기록을 불러오고(load) console에 보여주고 있어요.
게임의 기록을 저장하고 불러오는 것에 한정하고 console에 보여주는 책임을 다른 컴포넌트에게 부여하면 책임을 명확히 분리할 수 있어요.
// 저장된 게임 기록 출력
func showRecords() {
if records.isEmpty { //배열이 비었을 경우
print("저장된 게임 기록이 없습니다.")
} else { //게임 기록 출력
print("게임 기록:")
for (index, record) in records.enumerated() {
print("게임 \(index + 1): \(record)회") //index의 숫자보다 +1회하면 게임을 한 횟수가 됨!
}
}
}
//BaseballGame.swift의 func start()
case "2":
RecordManager.showRecords() // 게임 진행 상황을 RecordManager에서 출력
// 저장된 게임 기록 출력
func showRecords() -> String {
var recordResult: String = ""
if records.isEmpty { //배열이 비었을 경우
recordResult = "저장된 게임 기록이 없습니다."
} else {
for (index, record) in records.enumerated() {
recordResult = "게임 \(index + 1): \(record)회" //index의 숫자보다 +1회하면 게임을 한 횟수가 됨!
}
}
return recordResult // 게임 기록을 반환
}
//BaseballGame.swift의 func start()
case "2": // 게임 진행 상황 호출
print(recordManager.showRecords()) //출력
피드백 내용:
if strike == 3 처럼 3을 직접 비교하는 걸 Magic Number를 사용한다고 말해요.
let maxStrike = 3 또는 이름공간을 만들어서 상수를 따로 관리하면 맥락이 드러나고 정책이 변했을 때(게임의 규칙이 변했을 때) 코드로 대응하기 용이해집니다.
class BaseballGame {
var input : [Int] = [] //사용자가 입력할 숫자 3자리를 담는 배열
var randomNums : [Int] = [] // 정답 숫자 3자리를 담는 배열
let strikeCount = 3 //스트라이크 3개면 정답
let minCount = 0
수정 받은 코드
for (idx, inputNumber) in input.enumerated() where answer.contains(inputNumber) {
guard inputNumber == answer[idx] else {
ball += 1
continue
}
strike += 1
}
for i in 0..<input.count {
//스트라이크 계산
if input[i] == randomNums[i] {
strike += 1
} else if randomNums.contains(input[i]) { // else if 사용
ball += 1
}
}
피드백 내용:
커밋의 작업단위가 너무 큽니다😭
[FEAT] 야구 게임 기능 추가를 보면 게임의 핵심 기능 작성을 하나의 커밋에 모두 하셨어요.
입력 유효성 검사, 정답 생성, 스트라이크 계산, 볼 계산 등이 모두 작업단위로 분리될 수 있어보입니다.
코드를 작성하기 전에 무엇을 만들어야 하는 지 리스트를 작성해보면 작업단위를 나누는데 도움이 되기도 합니다.
이 부분은 아직 노력 중.. 사실 피드백 받고 수정한 과제 또한.. 한 번에 다 수정해버려서 ㅜㅜ..
앞으로 나의 룰은 피드백 받은대로
1. 작업 순서 미리 정하기
2. 한 작업 끝날 때마다 커밋하기 (습관 들이기)
피드백 받고 수정할 때에도,
수정 사항 정리 후 한 항목 수정 완료시 마다 커밋하기!
저도 마음속에 커밋해주세요.