Sparta Swift Homework #1 ... Number BaseBall Game ⚾️

TaeUk·2024년 3월 15일
0

Swift Project 정리

목록 보기
1/3
post-thumbnail

과제 주제 📝

Swift 기초, 심화 과정에서 배운 내용들을 종합하여 "숫자 야구 게임"을 만드는 것이 목표이다!

구현해야 되는 기능으로 필수 구현 기능이 있고, 부가적으로 선택 구현 기능이 있다.

  • 필수 구현 기능

    • Lv1
      • 1 ~ 9까지의 서로 다른 임의의 수 3개를 정하고 맞추는 게임입니다.
      • 정답은 랜덤으로 만듭니다.(1에서 9까지의 서로 다른 임의의 수 3자리)
    • Lv2
      • 정답을 맞추기 위해 3자리수를 입력하고 힌트를 받습니다.
      • 힌트는 야구용어인 볼과 스트라이크입니다.
      • 같은 자리에 같은 숫자가 있는 경우 스트라이크, 다른 자리에 숫자가 있는 경우 볼입니다.
      • 만약 올바르지 않은 입력값에 대해서는 오류 문구를 보여주세요.
      • 3자리 숫자가 정답과 같은 경우 게임이 종료됩니다.
  • 선택 구현 기능

    • Lv3

      • 정답이 되는 숫자를 0에서 9까지의 서로 다른 3자리의 숫자로 바꿔주세요.
      • 맨 앞자리에 0이 오는 것은 불가능합니다.
    • Lv4

      • 프로그램을 시작할 때 안내문구를 보여주세요.
        환영합니다! 원하시는 번호를 입력해주세요
         1. 게임 시작하기  2. 게임 기록 보기  3. 종료하기
      • 1번 게임 시작하기의 경우 “필수 구현 기능” 의 예시처럼 게임이 진행됩니다.
      • 정답을 맞혀 게임이 종료된 경우 위 안내문구를 다시 보여주세요.
        환영합니다! 원하시는 번호를 입력해주세요
         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()

Lv.1 🐶

필수 구현 기능

  • 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] 타입으로 저장하였다.


Lv.2 🐵

필수 구현 기능

  • 정답을 맞추기 위해 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문을 만들어 "정답"이 된 상황에서만 게임이 끝나도록 만들었다.


Lv.3 🦁

선택 구현 기능

  • 정답이 되는 숫자를 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 ~ 6 🐯🐻🐱

선택 구현 기능

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 내에 gameCountergameCounterLog를 만들었다.

start() 내에서 오답과 정답이 발생할 때마다 gameCounter가 1 씩 추가되도록 만들었으며, 이를 gameCounterLog에 저장하여 게임 횟차마다의 시도 횟수를 저장할 수 있게 만들었다.

그리고 게임 기록을 보기 위해 시작 메뉴에서 2번을 누르면, gameCounterLog에 저장된 값을 순차적으로 불러와 기록을 출력한다.

만약 게임을 시도하지 않아 gameCounterLog가 비어있을 때 배열을 출력하려 시도하면 런타임 에러가 발생한다. 이를 해결하기 위해 isEmpty를 활용하여 gameCounterLog에 접근하였다.

마지막으로 Lv.6의 프로그램 종료를 위해 startScreen() 안 while문 내의 조건문에서 3번을 선택하면 break가 되도록 만들었다. 또한 gameCounterLog을 []으로 만들어 내부 값을 휘발시켰다.

또한 추가 기능인 1 ~ 3 이외의 값을 입력하면 동작하지 않도록 조건문을 활용하여 해당 기능을 구현하였다.


과제를 마무리 하며... 🔍

일단 주어진 조건에 맞추어 잘 동작하도록 마무리는 잘 하였다!

모든 기능이 정상적으로 동작하며, 문제가 발생할 수 있는 예외 사항에서도 아무 에러 없이 잘 구현하였다!!! 🤯

하지만 너무 기능 구현에만 초점을 두고 작성하였기에, 코드가 너무 난잡하다...

그래서 가독성을 중점으로 switch나 삼항연산자 등을 활용하여 최종 코드를 수정해보았다.

swift Number BaseBall Game(GitHub - Tae Ouk)

다음 과제부터는 이러한 부분도 고려해서 코드를 작성해야겠다!

안그러면 팀프로젝트에서 팀원들과 협업하기 어려울거 같다... 😢

0개의 댓글