Swift 기초, 심화 과정에서 배운 내용들을 종합하여 "숫자 야구 게임"을 만드는 것이 목표이다!
구현해야 되는 기능으로
필수 구현 기능
이 있고, 부가적으로선택 구현 기능
이 있다.
필수 구현 기능
선택 구현 기능
Lv3
Lv4
환영합니다! 원하시는 번호를 입력해주세요
1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기
환영합니다! 원하시는 번호를 입력해주세요
1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기
1 // 1번 게임 시작하기 입력
< 게임을 시작합니다 >
숫자를 입력하세요
.
.
.
Lv5
2번 게임 기록 보기의 경우 완료한 게임들에 대해 시도 횟수를 보여줍니다.
환영합니다! 원하시는 번호를 입력해주세요
1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기
2 // 2번 게임 기록 보기 입력
< 게임 기록 보기 >
1번째 게임 : 시도 횟수 - 14
2번째 게임 : 시도 횟수 - 9
3번째 게임 : 시도 횟수 - 12
.
.
.
Lv6
3번 종료하기의 경우 프로그램이 종료됩니다.
환영합니다! 원하시는 번호를 입력해주세요
1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기
3 // 3번 종료하기 입력
< 숫자 야구 게임을 종료합니다 >
이전의 게임 기록들도 초기화됩니다.
1, 2, 3 이외의 입력값에 대해서는 오류 메시지를 보여주세요.
환영합니다! 원하시는 번호를 입력해주세요
1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기
4
올바른 숫자를 입력해주세요!
위의 조건에 맞게 총 Lv.6까지의 기능을 구현하여 과제를 마무리하였다!
해당 프로젝트는 Playground가 아닌 Xcode command line tool을 활용하여 구성되어 있다!
Xcode command line tool 을 이용하여 직접 입력값 받을 수 있기에 과제를 디버깅하기에는 매우 안성맞춤이였다.
Xcode상단탭 - File - New - Project - macOS의 Command Line Tool로 프로젝트 생성
// readLine() 함수를 이용하여 유저의 입력값 처리하기
// readLine() 함수에 대해 학습해보고 활용하기
let input = readLine()
필수 구현 기능
- 1 ~ 9까지의 서로 다른 임의의 수 3개를 정하고 맞추는 게임입니다.
- 정답은 랜덤으로 만듭니다.(1에서 9까지의 서로 다른 임의의 수 3자리)
맨 처음을 어떻게 시작할지 감이 안왔는데, 마침 기본 뼈대 코드가 제공되어서 이를 활용하였다!
코드 뼈대
// main.swift 파일
// 프로젝트 생성시 자동 생성됨
let game = BaseballGame()
game.start() // BaseballGame 인스턴스를 만들고 start 함수를 구현하기
// BaseballGame.swift 파일 생성
class 혹은 struct {
func start() {
let answer = makeAnswer() // 정답을 만드는 함수
}
func makeAnswer() -> Int {
// 함수 내부를 구현하기
}
}
BaseballGame.swift 파일 생성을 별도로 생성해서 프로젝트를 구성해라고 안내하였지만, main.swift 파일 내에서 모든 코드를 작성하였다. (꼼꼼히 읽어볼껄... 🥲)
// Lv.1 Number BaseBall Game
let game = BaseballGame()
game.start()
struct BaseballGame {
func start() {
let answer = makeAnswer()
print("Start Number BaseBall Game")
print("Input Your Number")
print(makeAnswer())
let input = readLine()!
let IntAnswer = String(input).map{ Int(String($0))! }
// 입력받은 input을 String -> [Int]로 변환
print(IntAnswer)
}
func makeAnswer() -> [Int] {
var ansMake = Array(1...9)
var randomValues: [Int] = []
while randomValues.count < 3 {
let randomIndex = Int.random(in: 0..<ansMake.count)
let randomNumber = ansMake[randomIndex]
if !randomValues.contains(randomNumber) {
randomValues.append(randomNumber)
}
}
/*
var ansMake: [Int] = []
for i in 0...2 {
ansMake.append(Int.random(in: 1...9))
}
// ⚠️ 랜덤하게 추가된 ansMake의 요소 중 중복이 발생할 수 있음 -> ✅ 해결!
*/
return randomValues
}
}
BaseballGame 안의 start()와 makeAnswer()를 만들어 기능을 구분하여 코드를 작성하였다.
먼저 makeAnswer()를 살펴보면, while 문을 사용하여 3자리의 랜덤값이 쌓일 수 있도록 만들었으며 내부에 조건문을 활용하여 이미 입력된 값이 중복되지 않도록 만들었다!
맨 처음엔 주석처리 된 코드를 작성하였는데, 중복이 발생할 수 있다는 것을 깨닫고 바로 수정하였다.
그리고 start()에서는 입력받은 map함수를 활용하여 입력받은 String 타입의 입력을 [Int] 타입으로 저장하였다.
필수 구현 기능
- 정답을 맞추기 위해 3자리수를 입력하고 힌트를 받습니다.
- 힌트는 야구용어인 볼과 스트라이크입니다.
- 같은 자리에 같은 숫자가 있는 경우 스트라이크, 다른 자리에 숫자가 있는 경우 볼입니다.
- 만약 올바르지 않은 입력값에 대해서는 오류 문구를 보여주세요.
- 3자리 숫자가 정답과 같은 경우 게임이 종료됩니다.
다음으로 필수 구현 기능 중 마지막인 Lv.2 코드이다!
아래의 Lv.3 ~ 6보다 해당 레벨에서 많은 시간이 소요되었다...
// Lv.2 Number BaseBall Game
let game = BaseballGame()
game.start()
struct BaseballGame {
func start() {
let answer = makeAnswer()
var isCorrect = false
print("Number BaseBall Game 시작!")
print("숫자를 입력하세요.")
print(answer)
while !isCorrect {
var IntAns = valueInput()
if valueComparison(answer, IntAns) {
print("정답!")
isCorrect = true
} else {
print("틀렸습니다... 다시 시도해주세요")
}
}
}
func valueInput() -> [Int] {
var input = readLine()!
if input.allSatisfy({ $0.isNumber }) {
// 입력된 값이 숫자로 구성되어 있는지 검증
let inputAns = input.map { Int(String($0))! }
// 입력받은 input을 String -> [Int]로 변환
if inputAns.count < 3 || inputAns.count > 3 {
print("올바르지 않은 입력값입니다 - 3가지 숫자를 입력해주세요")
} // 입력받은 수가 총 3 자리가 맞는지 검증
else if inputAns[0] == inputAns[1] || inputAns[1] == inputAns[2] || inputAns[0] == inputAns[2]{
print("올바르지 않은 입력값입니다 - 서로 다른 숫자를 입력해주세요")
} // 입력받은 수에서 중복되는 수가 있는 경우 검증
else if inputAns.contains(0) {
print("올바르지 않은 입력값입니다 - 0이 아닌 숫자를 입력해주세요")
} // 입력받은 수에서 0이 있는 경우 검증
return inputAns
}
else {
print("올바르지 않은 입력값입니다 - 숫자만 입력해주세요")
return [0,0,0]
}
// 숫자로 변환할 수 없는 입력에 대한 에러 처리
}
func valueComparison(_ valArr: [Int], _ inputArr: [Int]) -> Bool {
var ballCounter: Int = 0
var strikeCounter: Int = 0
var warningCounter: Int = 0
for i in 0..<valArr.count {
if inputArr.count == 3 && inputArr != [0,0,0] && !inputArr.contains(0) {
// 에러 요소가 발생시, 카운터 동작 정지 - 3자리 미만 또는 이상, 문자 입력시([0,0,0]), 0을 포함
if valArr[i] == inputArr[i] {
strikeCounter += 1
}
else if valArr.contains(inputArr[i]) {
ballCounter += 1
}
else if !valArr.contains(inputArr[i]) {
warningCounter += 1
}
}
} // ball & strike 구분 + 일치하는 값이 없을 때, Nothing을 출력하기 위한 경고 신호 발생
if inputArr.count == 3 && inputArr != [0,0,0] && !inputArr.contains(0) {
if warningCounter != 3 {
print("Balls: \(ballCounter), Strikes: \(strikeCounter)")
}
else {
print("Nothing")
}
} // 입력값과 정답을 비교하여 하나도 일치하지 않을 경우, "Nothing" 출력
// warningCounter != 3 ? print("Balls: \(ballCounter), Strikes: \(strikeCounter)") : print("Nothing")
warningCounter = 0
// 경고 초기화
return strikeCounter == 3
}
func makeAnswer() -> [Int] {
.
.
Lv.1 코드와 동일
.
.
}
}
기존 코드에서 입력된 값에서 게임 조건인 3자리 숫자만 입력되었는지 판단하는 valueInput()와 입력된 값과 랜덤값을 비교하여 힌트
를 출력하는 valueComparison() 함수를 추가하였다.
valueInput() 내부에는 많은 조건문을 볼 수 있는데, isNumber
를 활용하여 숫자 입력 여부를 확인할 수 있고, 그 이외의 다른 입력 조건을 배제하기 위한 기능임을 알 수 있다!
valueInput() 함수의
[Int]
라는 리턴값이 존재하기 때문에, 어떠한 조건에서도 값을 반환해야 했다. 그래서 만약 입력값이 숫자가 아니면[0,0,0]
을 반환하게 되는데, 게임 기능에 영향을 미치지 않는 더미값으로 전송하였다.
valueComparison()에서는 볼, 스트라이크와 같은 힌트 이외에도 일치하는 수가 없을 경우 "Nothing"을 출력하기 위해 warningCounter
을 만들어 해당 조건에 알맞은 기능을 하도록 설계하였다.
마지막으로 start() 내에서 게임을 실행하고 입력값을 넣으면 생성된 랜덤값과 비교하게 된다. 하지만 한 번 비교하고 프로그램이 종료되었기에, 이를 해결하기 위해 while문을 만들어 "정답"이 된 상황에서만 게임이 끝나도록 만들었다.
선택 구현 기능
- 정답이 되는 숫자를 0에서 9까지의 서로 다른 3자리의 숫자로 바꿔주세요.
- 맨 앞자리에 0이 오는 것은 불가능합니다.
lv.3에서는 코드가 길어서 가독성을 위해 해당 조건의 기능을 하는 함수만 설명하겠다!
func makeAnswer() -> [Int] {
let ansMake = Array(0...9)
var randomVal: [Int] = []
while randomVal.count < 3 {
let randomIndex = Int.random(in: 0..<ansMake.count)
let randomNum = ansMake[randomIndex]
if !randomVal.contains(randomNum) {
randomVal.isEmpty ? randomVal.append(Int.random(in: 1...9)) : randomVal.append(randomNum)
// ⚠️ 맨 처음 배열은 공백이기에 0번째 인덱스로 접근하면 에러 발생
}
}
return randomVal
}
기존 makeAnswer() 함수는 1 ~ 9 까지의 숫자를 활용한 3자리 랜덤값을 만드는 것이였다. 하지만 이번 조건에 맞추기 위해서는 ansMake
를 0 ~ 9 까지의 배열로 만들고 랜덤값을 저장하는 배열의 0번째 인덱스만 조심하면 된다.
위 코드의 주석에서 볼 수 있듯이, 맨 처음 랜덤 배열을 가져왔을 때는 배열 내부가 비어있기에 isEmpty
를 활용하여 접근하였다.
그리고 삼항연산자를 사용하여, 0번째 인덱스에 1 ~ 9 까지의 값을 저장해주었다.
배열의 0번째 인덱스 값이 입력되면, 이후의 1과 2 인덱스에는 0 ~ 9 까지의 숫자가 입력되게 된다!
선택 구현 기능
Lv.4
- 프로그램을 시작할 때 안내문구를 보여주세요.
- 1번 게임 시작하기의 경우 “필수 구현 기능” 의 예시처럼 게임이 진행됩니다
- 정답을 맞혀 게임이 종료된 경우 안내문구를 다시 보여주세요.
Lv.5
- 2번 게임 기록 보기의 경우 완료한 게임들에 대해 시도 횟수를 보여줍니다.
Lv6
- 3번 종료하기의 경우 프로그램이 종료됩니다.
- 이전의 게임 기록들도 초기화됩니다.
- 1, 2, 3 이외의 입력값에 대해서는 오류 메시지를 보여주세요.
Lv. 4 ~ 6는 코드를 작성하는 과정에 있어, 구현해야 되는 기능이 유사하기에 한번에 설명하겠다!
// Lv.2 Number BaseBall Game
var game = BaseballGame()
game.startScreen()
struct BaseballGame {
var gameCounter: Int = 0
var gameCounterLog: [Int] = []
// 전역변수 gameCounterLog를 활용한 게임 기록 저장
mutating func startScreen() {
var isCorrect = false
while !isCorrect {
print("환영합니다! 원하시는 번호를 입력해주세요")
print("1. 게임 시작하기 2. 게임 기록 보기 3. 종료하기")
var input = readLine()!
if input.allSatisfy({ $0.isNumber }) {
let inputAns = input.map { Int(String($0))! }
if inputAns.count == 1 && ( inputAns.contains(1) || inputAns.contains(2) || inputAns.contains(3) ) {
if inputAns[0] == 1 {
start()
} // 선택지 1번 선택시 -> start() 실행
else if inputAns[0] == 2 {
if !gameCounterLog.isEmpty {
for i in 0..<gameCounterLog.count {
print("\(i+1)번째 게임 : 시도 횟수 - \(gameCounterLog[i])")
}
} // ⚠️ 게임 기록이 없어 각각의 Log를 저장하는 배열이 비었을때, i에 해당하는 인덱스를 검색하면 에러 발생
else {
print("Game 기록이 없습니다...")
}
}
else if inputAns[0] == 3 {
print("< 숫자 야구 게임을 종료합니다 >")
gameCounterLog = []
break
}
}
else {
print("올바르지 않은 입력값입니다 - 보기의 숫자만 입력해주세요( 1 ~ 3 )")
}
}
else {
print("올바르지 않은 입력값입니다 - 숫자만 입력해주세요")
}
}
isCorrect = true
}
mutating func start() {
.
.
나머지 Lv.2 코드와 동일
.
.
while !isCorrect {
var IntAns = valueInput()
if valueComparison(answer, IntAns) {
print("정답!")
isCorrect = true
gameCounter += 1
gameCounterLog.append(gameCounter)
}
else {
print("틀렸습니다... 다시 시도해주세요")
gameCounter += 1
}
}
}
func valueInput() -> [Int] {
.
.
if inputAns.count != 3 {
print("올바르지 않은 입력값입니다 - 3가지 숫자를 입력해주세요")
}
else if inputAns[0] == inputAns[1] || inputAns[1] == inputAns[2] || inputAns[0] == inputAns[2]{
print("올바르지 않은 입력값입니다 - 서로 다른 숫자를 입력해주세요")
}
나머지 Lv.2 코드와 동일
.
.
}
func valueComparison(_ valArr: [Int], _ inputArr: [Int]) -> Bool {
.
.
if 조건문 -> inputArr.count == 3 && inputArr != [0,0,0] 으로 수정
나머지 Lv.2 코드와 동일
.
.
}
func makeAnswer() -> [Int] {
.
.
Lv.3 코드와 동일
.
.
}
}
우선 Lv.4를 구현하기 위해 startScreen()라는 함수를 만들었다. 그리고 1번을 선택하면 게임이 동작하도록, 해당 함수 내부에서 이전의 게임 실행을 담당하던 start()를 호출하도록 구현하였다.
다음으로 Lv.5에서 게임 기록을 보기 위해 BaseballGame 내에 gameCounter
와 gameCounterLog
를 만들었다.
start() 내에서 오답과 정답이 발생할 때마다 gameCounter가 1 씩 추가되도록 만들었으며, 이를 gameCounterLog에 저장하여 게임 횟차마다의 시도 횟수를 저장할 수 있게 만들었다.
그리고 게임 기록을 보기 위해 시작 메뉴에서 2번을 누르면, gameCounterLog에 저장된 값을 순차적으로 불러와 기록을 출력한다.
만약 게임을 시도하지 않아 gameCounterLog가 비어있을 때 배열을 출력하려 시도하면 런타임 에러가 발생한다. 이를 해결하기 위해
isEmpty
를 활용하여 gameCounterLog에 접근하였다.
마지막으로 Lv.6의 프로그램 종료를 위해 startScreen() 안 while문 내의 조건문에서 3번을 선택하면 break
가 되도록 만들었다. 또한 gameCounterLog을 []
으로 만들어 내부 값을 휘발시켰다.
또한 추가 기능인 1 ~ 3 이외의 값을 입력하면 동작하지 않도록 조건문을 활용하여 해당 기능을 구현하였다.
일단 주어진 조건에 맞추어 잘 동작하도록 마무리는 잘 하였다!
모든 기능이 정상적으로 동작하며, 문제가 발생할 수 있는 예외 사항에서도 아무 에러 없이 잘 구현하였다!!! 🤯
하지만 너무 기능 구현에만 초점을 두고 작성하였기에, 코드가 너무 난잡하다...
그래서 가독성을 중점으로 switch
나 삼항연산자 등을 활용하여 최종 코드를 수정해보았다.
다음 과제부터는 이러한 부분도 고려해서 코드를 작성해야겠다!
안그러면 팀프로젝트에서 팀원들과 협업하기 어려울거 같다... 😢