스파르타코딩 클럽/개인과제

10. 스파르타 코딩클럽 [본캠프 - 숫자야구 게임 최종]

UDDT 2025. 3. 23. 13:18

 

Ch 2. 프로그래밍 심화 주차 과제

 Lv. 1 코드 비교하기

Lv .1 튜터 작성 코드

class BaseballGame {
    func start() {

    }

    func makeAnswer() -> [Int] {
        let arr = (1 ... 9).map { $0 }
        
        // shuffle은 arr 자체를 섞는 것. 만약 shuffled로 쓰려면 새로운 변수(상수)에 저장해줘야 함
        let shuffledArray = arr.shuffled()

        return [Int](shuffledArray[0 ... 2])
    }
}

 

   1 ~ 10까지의 범위를 mapping하여 배열에 담아준다.

담긴 값은 [1, 2, 3, 4 ..., 9] 이고, 해당 값을 shuffle 한다.

 

 만약 return을 할 때 shuffledArray를 [Int] 배열로 Type을 변경해주지 않으면,

Cannot convert return expression of type 'Array<Int>.SubSequence' (aka 'ArraySlice<Int>') to return type '[Int]'

위와 같은 에러 메시지가 출력된다.

 

 arr.shuffled()를 하면, shuffledArray은 Array<Int> Type으로 저장되는 것이 맞다. 

그러나, shuffledArray[0 ... 2]는 ArraySlice<Int> Type이므로,

Array<Int>로 형변환를 해줘야 한다. 

[Int](shuffledArray[0 ... 2])는 ArraySlice<Int>를 Array<Int>로 형변환한 문법이다.

 

Lv .1 내가 작성한 코드

class BaseballGame {

    func makeAnswer() -> Int {
        let randomNumber1 = Int.random(in: 1...9)
        var randomNumber2 = Int.random(in: 1...9)
        var randomNumber3 = Int.random(in: 1...9)

        while randomNumber1 == randomNumber2 || randomNumber1 == randomNumber3 || randomNumber2 == randomNumber3 {
            randomNumber2 = Int.random(in: 1...9)
            randomNumber3 = Int.random(in: 1...9)
        }

        let computerChoice = [randomNumber1, randomNumber2, randomNumber3]

        let strComputerChoice = computerChoice.map(String.init).joined()
        guard let intComputerChoice = Int(strComputerChoice) else { return 0 }
        return intComputerChoice
    }
}

 

  내가 작성한 코드는 randomNumber1, 2, 3의 자리를 일일히 다 만들어주었다.

스파르타에서 제공해준 틀의 return Type이 Int로 되어 있어서 해당 타입에 포커스를 맞추다보니,

배열을 만들고, 다시 그 배열을 합치고, 합쳐진 값을 Int Type으로 바꾸는 불필요한 과정이 포함되었다.

 

 생각해보면 그냥 makeAnswer()의 Return Type을 Int가 아니라 [Int]로 설정했으면 간단하게 해결되었을 문제다.

코드를 작성하면서 한 곳에 너무 매몰되면 융통성이 부족해진다는 것을 느꼈다.

 

 Lv. 2 코드 비교하기

Lv .2 튜터 작성 코드

class BaseballGame {
    func start() {

        // makeAnswer의 값을 answer에 담는다
        let answer = makeAnswer()

        print("게임을 시작합니다")

        while true {
            print("숫자를 입력하세요: ", terminator: "")
            // userInput을 받는다
            let input = readLine() ?? ""

            // input을 mapping하여 [String] Type으로 변경하고, [Int]로 변경이 가능한 값만 남겨준다
            let numberArray = input.map { String($0) }.compactMap { Int($0) }

            // 변경된 값을 validateInput 함수에 넣어준다. 여기서 Bool Type이 Return 되고,
            let isValidate =  validateInput(numberArray)

            // 아래에서 조건을 확인한다. valiedate 함수에서 count가 3일 때만 true로 변경된다.
            guard isValidate else {
                print("올바르지 않은 입력 값입니다.")
                continue
            }

            var strike = 0
            var ball = 0

            // 사용자가 입력한 숫자 순회 (.enumerated로 Index도 함께 받음(튜플))
            for (index, number) in numberArray.enumerated() {
                if number == answer[index] {
                    strike += 1
                    continue
                }
                if answer.contains(number) {
                    ball += 1
                    continue
                }
            }

            if strike == 0 && ball == 0 {
                print("Nothing")
            } else if strike == 3 {
                print("정답입니다!")
                break
            } else {
                print("\(strike)스트라이크", terminator: " ")
                print("\(ball)볼")
            }
            print()
        }
    }
    
    func validateInput(_ input: [Int]) -> Bool {
        return input.count == 3
    } 
  
    func makeAnswer() -> [Int] {
        let arr = (1 ... 9).map{ $0 }
        // shuffle은 arr 자체를 섞는 것. 만약 shuffled로 쓰려면 새로운 변수(상수)에 저장해줘야 함

        let shuffledArray = arr.shuffled()

        return [Int](shuffledArray[0...2])
    }
}

 

  튜터님이 작성한 코드는 굉장히 간결하다.

내가 주목하게 된 포인트는 2개이다.

 

 1. Chaining

let numberArray = input.map { String($0) }.compactMap { Int($0) }

 

위의 코드는 아래처럼 2번 바꿔줘야 하는데, 이를 체이닝 하면 위의 코드처럼 한줄로 작성이 가능하다.

let numberArray = input.map { String($0) }
let intArray = numberArray.compactMap { Int($0) }

 

 

 2. 함수의 사용

    let isValidate =  validateInput(numberArray)

    func validateInput(_ input: [Int]) -> Bool {
        return input.count == 3
    }

 

  validateInput 함수는 numberArray 배열에 3개의 값이 저장되어 있는지를 확인하는 함수다.

validateInput 함수의 return Type이 Bool Type이기 때문에,

isValidate에는 true인지 false인지가 담긴다.

이 조건을 확인한 후 다음의 코드를 실행한다.

guard isValidate else {
            print("올바르지 않은 입력 값입니다.")
            exit(0)
        }

 

 isValidate이 true면 guard는 무시하고 다음의 코드를 실행하고,

isValidate이 false면 "올바르지 않은 입력 값입니다."라는 로그가 출력된다.

 

 Lv.2 과제를 비교하면서,

함수를 선언할 때 어떤 Type을 사용할지,

내부 로직은 어떻게 구현할지 고민하고

더 많이 연습하면 코드가 더 잘 정리될 것이라는 생각이 들었다.

Lv .2 내가 작성한 코드

class BaseballGame {
    func start() {
        let answer = makeAnswer()
        print("서로 다른 숫자 3개를 입력해주세요(범위: 1 ~ 9, 예: 356): ", terminator: "")
        while true {
            let userInput = getUserInput()
            if userInput == 0 {
                print("잘못된 입력입니다. 다시 입력해주세요: ",terminator: "")
            } else if userInput != 0 {
                let result = checkAnswer(computerChoice: answer, userChoice: userInput)
                print(result)

                if result == "정답입니다!" {
                    break
                } else {
                    print("다시 입력해주세요: ", terminator: "")
                }
            }
        }

    }

    func makeAnswer() -> Int {
        let randomNumber1 = Int.random(in: 1...9)
        var randomNumber2 = Int.random(in: 1...9)
        var randomNumber3 = Int.random(in: 1...9)

        while randomNumber1 == randomNumber2 || randomNumber1 == randomNumber3 || randomNumber2 == randomNumber3 {
            randomNumber2 = Int.random(in: 1...9)
            randomNumber3 = Int.random(in: 1...9)
        }

        let computerChoice = [randomNumber1, randomNumber2, randomNumber3]

        let strComputerChoice = computerChoice.map(String.init).joined()
        guard let intComputerChoice = Int(strComputerChoice) else { return 0 }
        return intComputerChoice
    }

    func getUserInput() -> Int {
        let userInput = readLine() ?? ""
        let ArrUserInput = Array(userInput)

        // 첫번째 index에 0 입력 시 예외처리 추가
        if ArrUserInput.first == "0" {
            return 0
        }
        // 중복되는 숫자 예외처리 추가
        let SetUserInput = Set(ArrUserInput)
        guard SetUserInput.count == 3 else { return 0 }

        guard let intUserInput = Int(userInput) else { return 0 }
        return intUserInput
    }

    func checkAnswer(computerChoice: Int, userChoice: Int) -> String {
        // int형인 숫자를 string으로 변경
        let computerChoice = String(computerChoice)
        let userChoice = String(userChoice)

        // string을 개별 String으로 배열에 저장
        let arrComputerChoice = Array(computerChoice)
        let arrUserChoice = Array(userChoice)

        var strikeCount = 0
        var ballCount = 0

        for (index1, value1) in arrComputerChoice.enumerated() {
            for (index2, value2) in arrUserChoice.enumerated() {
                if index1 == index2 && value1 == value2 {
                    strikeCount += 1
                } else if index1 != index2 && value1 == value2 {
                    ballCount += 1
                }
            }
        }

        if strikeCount == 0 && ballCount == 0 {
            return "Nothing"
        }

        let result = (strikeCount, ballCount)

        switch result {
        case (3, 0):
            return "정답입니다!"
        case (2, 0):
            return "\(strikeCount)스트라이크"
        case (1, 0):
            return "\(strikeCount)스트라이크"
        case (2, 1):
            return "\(strikeCount)스트라이크, \(ballCount)볼"
        case (1, 1):
            return "\(strikeCount)스트라이크, \(ballCount)볼"
        case (0, 3):
            return "\(ballCount)볼"
        case (0, 2):
            return "\(ballCount)볼"
        case (0, 1):
            return "\(ballCount)볼"
        default:
            return "잘못된 입력입니다"
        }
    }
}

    

 Lv. 3 코드 비교하기

Lv .3 튜터 작성 코드

    func makeAnswer() -> [Int] {
        let arr = (0 ... 9).map{ $0 }
        // shuffle은 arr 자체를 섞는 것. 만약 shuffled로 쓰려면 새로운 변수(상수)에 저장해줘야 함

        let shuffledArray = arr.shuffled()

        if shuffledArray[0] == 0 {
            return [Int](shuffledArray[1...3])
        } else {
            return [Int](shuffledArray[0...2])
        }

    }

 

 

   내가 보통 예외처리를 하면 다음과 같이 사고한다.

ex) 첫번째 자리에는 0이 들어가면 안될 때

 1. 0번째 값이 0이면 안되는구나.

    shuffledArray[0] != 0 

 2. 그다음 어떻게 하지?

     - Array[0]의 값만 다시 뽑을까?

     여기서부터 막혀버린다.

 

   반복해서 말하지만, '문제'에 매몰되면 문제를 해결할 수 없는 것 같다.

지금처럼 오히려 생각을 바꿔서, shuffledArray[0] == 0 일때의 return 값을 정해주는 것이

더 효율적일 때가 있다.

 

Lv .3 내가 작성한 코드

    func makeAnswer() -> Int {
        let randomNumber1 = Int.random(in: 1...9)
        var randomNumber2 = Int.random(in: 0...9)
        var randomNumber3 = Int.random(in: 0...9)

        while randomNumber1 == randomNumber2 || randomNumber1 == randomNumber3 || randomNumber2 == randomNumber3 {
            randomNumber2 = Int.random(in: 0...9)
            randomNumber3 = Int.random(in: 0...9)
        }

        let computerChoice = [randomNumber1, randomNumber2, randomNumber3]

        let strComputerChoice = computerChoice.map(String.init).joined()
        guard let intComputerChoice = Int(strComputerChoice) else { return 0 }
        return intComputerChoice
    }

 

   이전에 노가다(?)로 각 randomNumber의 자리를 만들어준 덕분인지, 

2번째 3번째 자리에 0이 포함되게 하는 것은 간단하게 해결할 수 있었다.

 

 Lv. 4 코드 비교하기

Lv .4 튜터 작성 코드

class BaseballGame {
    func start() {

        while true {
            showGreetingMessage()

            let option = readLine() ?? ""

            switch option {
            case "1":
                playOneGame()
            case "2":
                break
            case "3":
                break
            default:
                print("올바르지 않은 입력 값입니다.")
            }

            print("게임을 시작합니다")
        }
    }


    func makeAnswer() -> [Int] {
        let arr = (0 ... 9).map{ $0 }
        // shuffle은 arr 자체를 섞는 것. 만약 shuffled로 쓰려면 새로운 변수(상수)에 저장해줘야 함

        let shuffledArray = arr.shuffled()

        if shuffledArray[0] == 0 {
            return [Int](shuffledArray[1...3])
        } else {
            return [Int](shuffledArray[0...2])
        }
    }


    func validateInput(_ input: [Int]) -> Bool {
        return input.count == 3
    }

    func showGreetingMessage() {
        let greetingMessage = """
        환영합니다! 원하시는 번호를 입력해주세요
        1. 게임 시작하기    2. 게임 기록 보기    3. 종료하기 
        """

        print(greetingMessage)
    }

    func playOneGame() {
        let answer = makeAnswer()

        while true {
            print("숫자를 입력하세요: ", terminator: "")
            // userInput을 받는다
            let input = readLine() ?? ""

            // input을 mapping하여 [String] Type으로 변경하고, [Int]로 변경이 가능한 값만 남겨준다
            let numberArray = input.map { String($0) }.compactMap { Int($0) }

            // 변경된 값을 validateInput 함수에 넣어준다. 여기서 Bool Type이 Return 되고,
            let isValidate =  validateInput(numberArray)

            // 아래에서 조건을 확인한다. valiedate 함수에서 count가 3일 때만 true로 변경된다.
            guard isValidate else {
                print("올바르지 않은 입력 값입니다.")
                continue
            }

            var strike = 0
            var ball = 0

            // 사용자가 입력한 숫자 순회 (.enumerated로 Index도 함께 받음(튜플))
            for (index, number) in numberArray.enumerated() {
                if number == answer[index] {
                    strike += 1
                    continue
                }
                if answer.contains(number) {
                    ball += 1
                    continue
                }
            }

            if strike == 0 && ball == 0 {
                print("Nothing")
            } else if strike == 3 {
                print("정답입니다!")
                print()
                break
            } else {
                print("\(strike)스트라이크", terminator: " ")
                print("\(ball)볼")
            }
            print()
        }
    }
}

 

 """을 사용하면 여러 줄 String을 받을 수 있다는 것을 보고, 

내가 문법 공부를 게을리 했다는 생각이 들었다.

Lv .4 내가 작성한 코드

// StartSet.swift
class StartSet {
    enum SelectOption: String {
        case gameStart = "1"
        case printLog = "2"
        case gameFin = "3"
    }

    func setStart() {
        print("환영합니다! 원하시는 번호를 입력해주세요!: ")

        while true {
            print("1. 게임 시작하기   2. 게임 기록 보기    3. 종료하기")

            let setNumber = readLine() ?? ""
            if let option = SelectOption(rawValue: setNumber) {
                switch option {
                case .gameStart :
                    print("게임을 시작합니다")
                    game.start()
                    gameLogs.append(game.tryCount)
                case .printLog :
                    print("로그를 출력합니다")
                    //로그 출력 영역
                    continue
                case .gameFin :
                    print("게임을 종료합니다")
                    break
                }
            } else {
                print("잘못된 입력입니다. 1 ~ 3 까지 숫자만 입력해주세요: ")
            }
        }
    }
}


//main.Swift
let setting = StartSet()
setting.setStart()

 

 일반 String Type으로 비교하는 것보다 enum Type으로 비교하는 것이

안정성 측면에서 더 좋을 것 같아서 enum Type으로 switch를 했다.

 

 Lv. 5 ~ 6 코드 비교하기

Lv .5~6 튜터 작성 코드

//  BaeballGame.swift
class BaseballGame {

    func start() {
        var tryCountPerGame: [Int] = []

        while true {
            showGreetingMessage()

            let option = readLine() ?? ""

            switch option {
            case "1":
                print("게임을 시작합니다")
                let tryCount = playOneGame()
                tryCountPerGame.append(tryCount)
            case "2":
                showGameLog(tryCountPerGame)
            case "3":
                print("<숫자 야구 게임을 종료합니다>")
                exit(0)
            default:
                print("올바르지 않은 입력 값입니다.")
            }
        }
    }


    func makeAnswer() -> [Int] {
        let arr = (0 ... 9).map{ $0 }
        // shuffle은 arr 자체를 섞는 것. 만약 shuffled로 쓰려면 새로운 변수(상수)에 저장해줘야 함

        let shuffledArray = arr.shuffled()

        if shuffledArray[0] == 0 {
            return [Int](shuffledArray[1...3])
        } else {
            return [Int](shuffledArray[0...2])
        }
    }


    func validateInput(_ input: [Int]) -> Bool {
        return input.count == 3
    }

    func showGreetingMessage() {
        let greetingMessage = """
        환영합니다! 원하시는 번호를 입력해주세요
        1. 게임 시작하기    2. 게임 기록 보기    3. 종료하기 
        """

        print(greetingMessage)
    }

    // 반환값: 이번 게임의 시도 횟수
    func playOneGame() -> Int {
        let answer = makeAnswer()
        
        var tryCount = 0

        while true {
            print("숫자를 입력하세요: ", terminator: "")
            // userInput을 받는다
            let input = readLine() ?? ""
            tryCount += 1

            // input을 mapping하여 [String] Type으로 변경하고, [Int]로 변경이 가능한 값만 남겨준다
            let numberArray = input.map { String($0) }.compactMap { Int($0) }

            // 변경된 값을 validateInput 함수에 넣어준다. 여기서 Bool Type이 Return 되고,
            let isValidate =  validateInput(numberArray)

            // 아래에서 조건을 확인한다. valiedate 함수에서 count가 3일 때만 true로 변경된다.
            guard isValidate else {
                print("올바르지 않은 입력 값입니다.")
                continue
            }

            var strike = 0
            var ball = 0

            // 사용자가 입력한 숫자 순회 (.enumerated로 Index도 함께 받음(튜플))
            for (index, number) in numberArray.enumerated() {
                if number == answer[index] {
                    strike += 1
                    continue
                }
                if answer.contains(number) {
                    ball += 1
                    continue
                }
            }

            if strike == 0 && ball == 0 {
                print("Nothing")
            } else if strike == 3 {
                print("정답입니다!")
                print()
                return tryCount
            } else {
                print("\(strike)스트라이크", terminator: " ")
                print("\(ball)볼")
            }
            print()
        }
    }

    func showGameLog(_ tryCountPerGame: [Int]) {
        print("<게임 기록 보기>")
        for (index, tryCount) in tryCountPerGame.enumerated() {
            print("\(index + 1)번째 게임: 시도 횟수 - \(tryCount)회")
        }
    }
}


//main.Swift
let game = BaseballGame()
game.start() // BaseballGame 인스턴스를 만들고 start 함수를 구현하기

 

Lv .5 내가 작성한 코드

// BaseballGame.swift
class BaseballGame {
    var tryCount = 0

    func start() {
        let answer = makeAnswer()
        print("서로 다른 숫자 3개를 입력해주세요(범위: 1 ~ 9, 예: 356): ", terminator: "")
        while true {
            let userInput = getUserInput()
            if userInput == 0 {
                print("잘못된 입력입니다. 다시 입력해주세요: ",terminator: "")
            } else if userInput != 0 {
                let result = checkAnswer(computerChoice: answer, userChoice: userInput)
                print(result)

                if result == "정답입니다!" {
                    break
                } else {
                    print("다시 입력해주세요: ", terminator: "")
                }
            }
        }

    }

    func makeAnswer() -> Int {
        let randomNumber1 = Int.random(in: 1...9)
        var randomNumber2 = Int.random(in: 0...9)
        var randomNumber3 = Int.random(in: 0...9)

        while randomNumber1 == randomNumber2 || randomNumber1 == randomNumber3 || randomNumber2 == randomNumber3 {
            randomNumber2 = Int.random(in: 0...9)
            randomNumber3 = Int.random(in: 0...9)
        }

        let computerChoice = [randomNumber1, randomNumber2, randomNumber3]

        let strComputerChoice = computerChoice.map(String.init).joined()
        guard let intComputerChoice = Int(strComputerChoice) else { return 0 }
        return intComputerChoice
    }

    func getUserInput() -> Int {
        let userInput = readLine() ?? ""
        let ArrUserInput = Array(userInput)

        // 첫번째 index에 0 입력 시 예외처리 추가
        if ArrUserInput.first == "0" {
            return 0
        }
        // 중복되는 숫자 예외처리 추가
        let SetUserInput = Set(ArrUserInput)
        guard SetUserInput.count == 3 else { return 0 }

        guard let intUserInput = Int(userInput) else { return 0 }
        return intUserInput
    }

    func checkAnswer(computerChoice: Int, userChoice: Int) -> String {
        // int형인 숫자를 string으로 변경
        let computerChoice = String(computerChoice)
        let userChoice = String(userChoice)

        // string을 개별 String으로 배열에 저장
        let arrComputerChoice = Array(computerChoice)
        let arrUserChoice = Array(userChoice)

        var strikeCount = 0
        var ballCount = 0

        for (index1, value1) in arrComputerChoice.enumerated() {
            for (index2, value2) in arrUserChoice.enumerated() {
                if index1 == index2 && value1 == value2 {
                    strikeCount += 1
                } else if index1 != index2 && value1 == value2 {
                    ballCount += 1
                }
            }
        }

        if strikeCount == 0 && ballCount == 0 {
            tryCount += 1
            return "Nothing"
        }

        let result = (strikeCount, ballCount)

        switch result {
        case (3, 0):
            tryCount += 1
            return "정답입니다!"
        case (2, 0):
            tryCount += 1
            return "\(strikeCount)스트라이크"
        case (1, 0):
            tryCount += 1
            return "\(strikeCount)스트라이크"
        case (2, 1):
            tryCount += 1
            return "\(strikeCount)스트라이크, \(ballCount)볼"
        case (1, 1):
            tryCount += 1
            return "\(strikeCount)스트라이크, \(ballCount)볼"
        case (0, 3):
            tryCount += 1
            return "\(ballCount)볼"
        case (0, 2):
            tryCount += 1
            return "\(ballCount)볼"
        case (0, 1):
            tryCount += 1
            return "\(ballCount)볼"
        default:
            return "잘못된 입력입니다"
        }
    }
}


// StartSet.swift
let game = BaseballGame()

class StartSet {
    var gameLogs: [Int] = []

    enum SelectOption: String {
        case gameStart = "1"
        case printLog = "2"
        case gameFin = "3"
    }

    func setStart() {
        print("환영합니다! 원하시는 번호를 입력해주세요!: ")

        while true {
            print("1. 게임 시작하기   2. 게임 기록 보기    3. 종료하기")

            let setNumber = readLine() ?? ""
            if let option = SelectOption(rawValue: setNumber) {
                switch option {
                case .gameStart :
                    print("게임을 시작합니다")
                    game.start()
                    gameLogs.append(game.tryCount)
                case .printLog :
                    print("로그를 출력합니다")
                    printLog()
                    continue
                case .gameFin :
                    print("게임을 종료합니다")
                    break
                }
            } else {
                print("잘못된 입력입니다. 1 ~ 3 까지 숫자만 입력해주세요: ")
            }
        }
    }

    func printLog() {
        if gameLogs.isEmpty {
            print("입력된 게임 기록이 없습니다")
        } else {
            for (index, count) in gameLogs.enumerated() {
                print("\(index + 1)번째 게임 -  \(count)회 시도")
            }
        }
    }
}

//main.swift
let setting = StartSet()
setting.setStart()

 

Lv .5 ~ 6 가장 헷갈렸던 개념 : 함수의 호출과 사용

 뭔가 말로 정리하기 어려운 부분인데,

함수를 선언할 때는 parameter와 Return Type이 있다.

 

 함수의 값을 사용하고 해당 값을 받아줄 때
(특히 정의한 함수를 다른 함수에서 호출하여 사용할 때)

parameter와 Return Type을 어떻게 정의할지 깔끔하지 않다.

 '깔끔하지 않다'는 건, 머리 속으로 명료하게 정리가 안된 채로

함수를 작성한다는 것이다.

 

 나같이 처음 시작하는 모든 개발자가 그렇겠지만, 뭔가 뚝딱뚝딱 해내고 싶은데 잘 안될때가 많다.

 

 튜터님께서는 다른 사람들의 코드를 많이 들여다보면,

자연스럽게 틀이 조금씩 나올거라고 하셨는데
조금 더 부지런하게 코드를 씹고 뜯고 맛봐야겠다.